1#![forbid(unsafe_code)]
2#![deny(missing_docs)]
3#![deny(unused_must_use)]
4#![deny(unused_mut)]
5
6use anyhow::{bail, Error, Result};
20
21pub const DICTIONARY_UNIQUE_PREFIX: usize = 3;
28
29pub const DICTIONARY: [&str; 1024] = [
32 "abbey", "able", "abort", "absorb", "abyss", "ace", "ache", "acid", "across", "acumen",
33 "adapt", "adept", "adjust", "adopt", "adult", "aerial", "afar", "affair", "afield", "afloat",
34 "afoot", "afraid", "after", "age", "agile", "aglow", "agony", "agree", "ahead", "aid", "aisle",
35 "ajar", "akin", "alarm", "album", "alert", "alley", "almost", "aloof", "alps", "also",
36 "alumni", "always", "amaze", "ambush", "amidst", "ammo", "among", "ample", "amuse", "anchor",
37 "angle", "ankle", "antic", "anvil", "apart", "apex", "aphid", "aplomb", "apply", "arch",
38 "ardent", "arena", "argue", "arise", "arm", "around", "arrow", "ascend", "aside", "ask",
39 "asleep", "aspire", "asylum", "atlas", "atom", "atrium", "attire", "auburn", "audio", "august",
40 "aunt", "auto", "avatar", "avid", "avoid", "awful", "awning", "awoken", "axe", "axis", "axle",
41 "aztec", "azure", "baby", "bacon", "bad", "bail", "bakery", "bamboo", "banjo", "basin",
42 "batch", "bawl", "bay", "beer", "befit", "begun", "behind", "being", "below", "best", "bevel",
43 "beware", "beyond", "bias", "bid", "bike", "bird", "bite", "blip", "boat", "body", "bogey",
44 "boil", "bold", "bomb", "border", "boss", "both", "bovine", "box", "broken", "brunt", "bubble",
45 "budget", "buffet", "bug", "bulb", "bum", "bun", "but", "buy", "buzz", "byline", "bypass",
46 "cabin", "cactus", "cadet", "cafe", "cage", "cajun", "cake", "camp", "candy", "case", "cat",
47 "cause", "cease", "cedar", "cell", "cement", "cent", "chrome", "cider", "cigar", "cinema",
48 "circle", "claw", "click", "clue", "coal", "cobra", "cocoa", "code", "coffee", "cog", "coil",
49 "cold", "comb", "cool", "copy", "cousin", "cowl", "cube", "cuff", "custom", "dad", "daft",
50 "dagger", "daily", "dam", "dapper", "dart", "dash", "date", "dawn", "daze", "debt", "decay",
51 "deft", "deity", "den", "depth", "desk", "devoid", "dice", "diet", "dig", "dilute", "dim",
52 "dine", "diode", "ditch", "dive", "dizzy", "doctor", "dodge", "doe", "dog", "doing", "donut",
53 "dose", "dot", "double", "dove", "down", "doze", "dream", "drink", "drunk", "dry", "dual",
54 "dubbed", "dud", "duet", "duke", "dumb", "dune", "duplex", "dust", "duty", "dwarf", "dwelt",
55 "dying", "each", "eagle", "earth", "easy", "eat", "echo", "eden", "edgy", "edit", "eel", "egg",
56 "eight", "either", "eject", "elapse", "elbow", "eldest", "eleven", "elite", "elope", "else",
57 "elude", "email", "ember", "emerge", "emit", "empty", "energy", "enigma", "enjoy", "enlist",
58 "enmity", "enough", "ensign", "envy", "epoxy", "equip", "erase", "error", "estate", "etch",
59 "ethics", "excess", "exhale", "exit", "exotic", "extra", "exult", "fade", "fake", "fall",
60 "family", "fancy", "fatal", "fault", "fawn", "fax", "faze", "feast", "fee", "felt", "fence",
61 "ferry", "fever", "few", "fiat", "fibula", "fidget", "fierce", "fight", "film", "fir", "five",
62 "fix", "fizz", "fleet", "fly", "foam", "focus", "foe", "fog", "foil", "font", "fossil", "fowl",
63 "fox", "foyer", "frame", "frown", "fruit", "fry", "fudge", "fuel", "full", "fume", "fun",
64 "future", "fuzz", "gables", "gadget", "gag", "gain", "galaxy", "game", "gang", "gasp",
65 "gather", "gauze", "gave", "gawk", "gaze", "gecko", "geek", "gel", "germ", "geyser", "ghetto",
66 "ghost", "giant", "giddy", "gift", "gill", "ginger", "girth", "give", "glass", "glide", "gnaw",
67 "gnome", "goat", "goblet", "goes", "going", "gone", "gopher", "gossip", "got", "gown", "grunt",
68 "guest", "guide", "gulp", "guru", "gust", "gutter", "guy", "gypsy", "gyrate", "hair", "having",
69 "hawk", "haze", "heel", "heft", "height", "hence", "hero", "hide", "hijack", "hike", "hill",
70 "hinder", "hip", "hire", "hive", "hoax", "hobby", "hockey", "hold", "honk", "hook", "hop",
71 "horn", "hot", "hover", "howl", "huddle", "hug", "hull", "hum", "hunt", "hut", "hybrid",
72 "hyper", "icing", "icon", "idiom", "idle", "idol", "igloo", "ignore", "iguana", "impel",
73 "incur", "injury", "inline", "inmate", "input", "insult", "invoke", "ion", "irate", "iris",
74 "iron", "island", "issue", "itch", "item", "itself", "ivory", "jab", "jade", "jagged", "jail",
75 "jargon", "jaunt", "jaw", "jazz", "jeans", "jeer", "jest", "jewel", "jigsaw", "jingle", "jive",
76 "job", "jock", "jog", "joke", "jolt", "jostle", "joy", "judge", "juicy", "july", "jump",
77 "junk", "jury", "karate", "keep", "kennel", "kept", "kettle", "king", "kiosk", "kiss", "kiwi",
78 "knee", "knife", "koala", "lad", "lag", "lair", "lake", "lamb", "lap", "large", "last",
79 "late", "lava", "lay", "lazy", "ledge", "leech", "left", "legion", "lemon", "lesson", "liar",
80 "lick", "lid", "lie", "light", "lilac", "lime", "line", "lion", "liquid", "list", "live",
81 "load", "lock", "lodge", "loft", "logic", "long", "lopped", "lost", "loud", "love", "low",
82 "loyal", "lucky", "lump", "lung", "lurk", "lush", "luxury", "lymph", "lynx", "lyrics", "macro",
83 "mail", "major", "make", "male", "mammal", "map", "mate", "maul", "mayor", "maze", "mean",
84 "memoir", "men", "merge", "mesh", "met", "mew", "mice", "midst", "mighty", "mime", "mirror",
85 "misery", "moat", "mob", "mock", "mohawk", "molten", "moment", "money", "moon", "mop",
86 "morsel", "most", "mouth", "mow", "much", "mud", "muffin", "mug", "mullet", "mumble", "muppet",
87 "mural", "muzzle", "myriad", "myth", "nag", "nail", "name", "nanny", "nap", "nasty", "navy",
88 "near", "need", "neon", "nephew", "nerve", "nest", "never", "newt", "nexus", "nibs", "niche",
89 "niece", "nifty", "nimbly", "nobody", "nod", "noise", "nomad", "note", "noun", "nozzle",
90 "nuance", "nudged", "nugget", "null", "numb", "nun", "nurse", "nylon", "oak", "oar", "oasis",
91 "object", "occur", "ocean", "odd", "off", "often", "okay", "older", "olive", "omega", "onion",
92 "online", "onto", "onward", "ooze", "open", "opus", "orange", "orb", "orchid", "order",
93 "organ", "origin", "oscar", "otter", "ouch", "ought", "ounce", "oust", "oval", "oven", "owe",
94 "owl", "own", "oxygen", "oyster", "ozone", "pact", "page", "palace", "paper", "past", "pat",
95 "pause", "peel", "peg", "pen", "people", "pepper", "pest", "petal", "phase", "phone", "piano",
96 "pick", "pierce", "pimple", "pirate", "pivot", "pixel", "pizza", "plead", "pliers", "plus",
97 "poetry", "point", "poke", "pole", "pony", "pool", "pot", "pouch", "powder", "pray", "pride",
98 "prune", "pry", "public", "puck", "puddle", "puff", "pulp", "punch", "puppy", "purge", "push",
99 "putty", "pylon", "python", "queen", "quick", "quote", "radar", "raft", "rage", "rake",
100 "rally", "ram", "rapid", "rare", "rash", "rat", "rave", "ray", "razor", "react", "rebel",
101 "recipe", "reduce", "reef", "refer", "reheat", "relic", "remedy", "repent", "rerun", "rest",
102 "return", "revamp", "rewind", "rhino", "rhyme", "rib", "rich", "ride", "rift", "rigid", "rim",
103 "riot", "rip", "rise", "ritual", "river", "roar", "robot", "rodent", "rogue", "role", "room",
104 "rope", "roster", "rotate", "rover", "royal", "ruby", "rude", "rug", "ruin", "rule", "rumble",
105 "run", "rural", "sack", "safe", "saga", "sail", "sake", "salad", "sample", "sand", "sash",
106 "satin", "save", "scenic", "school", "scoop", "scrub", "scuba", "second", "sedan", "seed",
107 "setup", "sew", "sieve", "silk", "sip", "siren", "size", "skate", "skew", "skull", "slid",
108 "slow", "slug", "smash", "smog", "snake", "sneeze", "sniff", "snout", "snug", "soap", "sob",
109 "soccer", "soda", "soggy", "soil", "solve", "sonar", "soot", "sort", "sow", "soy", "space",
110 "speed", "sphere", "spout", "sprig", "spud", "spy", "square", "stick", "subtly", "suede",
111 "sugar", "sum", "sun", "surf", "sushi", "suture", "swept", "sword", "swung", "system", "tab",
112 "tacit", "tag", "taint", "take", "talent", "tamper", "tan", "task", "tattoo", "taunt",
113 "tavern", "tawny", "taxi", "tell", "tender", "tepid", "tether", "thaw", "thorn", "thumb",
114 "thwart", "ticket", "tidy", "tier", "tiger", "tilt", "timber", "tint", "tip", "tire", "tissue",
115 "titan", "today", "toffee", "toilet", "token", "tone", "top", "torn", "toss", "total", "touch",
116 "tow", "toxic", "toy", "trash", "trend", "tribal", "truth", "try", "tube", "tuck", "tudor",
117 "tuft", "tug", "tulip", "tune", "turn", "tusk", "tutor", "tuxedo", "twang", "twice", "tycoon",
118 "type", "tyrant", "ugly", "ulcer", "umpire", "uncle", "under", "uneven", "unfit", "union",
119 "unmask", "unrest", "unsafe", "until", "unveil", "unwind", "unzip", "upbeat", "update",
120 "uphill", "upkeep", "upload", "upon", "upper", "urban", "urge", "usage", "use", "usher",
121 "using", "usual", "utmost", "utopia", "vague", "vain", "value", "vane", "vary", "vat", "vault",
122 "vector", "veer", "vegan", "vein", "velvet", "vest", "vexed", "vial", "vice", "video",
123 "viking", "violin", "viper", "vital", "vivid", "vixen", "vocal", "vogue", "voice", "vortex",
124 "vote", "vowel", "voyage", "wade", "waffle", "waist", "wake", "want", "war", "water", "wax",
125 "wedge", "weird", "went", "wept", "were", "whale", "when", "whole", "wide", "wield", "wife",
126 "wiggle", "wild", "winter", "wire", "wise", "wives", "wizard", "wobbly", "woes", "woke",
127 "wolf", "woozy", "worry", "woven", "wrap", "wrist", "wrong", "yacht", "yahoo", "yank",
128];
129
130pub fn word_at_index(i: usize) -> String {
133 if i > 1023 {
134 panic!("attempt to access index {} but dictionary-1024 only has 1024 elements", i);
135 }
136 DICTIONARY[i].to_string()
137}
138
139pub fn index_of_word(word: &str) -> Result<usize, Error> {
143 if word.len() < 3 {
144 bail!("each word must have at least three characters");
145 }
146 let word = &word[..3];
147
148 for i in 0..1024 {
149 if &DICTIONARY[i][..3] == word {
150 return Ok(i);
151 }
152 }
153 bail!("word prefix '{}' was not found in dictionary", word);
154}
155
156pub fn words_match(a: &str, b: &str) -> bool {
160 let ai = match index_of_word(a) {
161 Ok(x) => x,
162 Err(_) => return false,
163 };
164 let bi = match index_of_word(b) {
165 Ok(x) => x,
166 Err(_) => return false,
167 };
168 ai == bi
169}
170
171#[cfg(test)]
172mod tests {
173 use super::*;
174
175 #[test]
176 fn basic_tests() {
177 for i in 0..1024 {
179 let word = word_at_index(i);
180 let index = index_of_word(&word).unwrap();
181 if index != i {
182 panic!("mismatch");
183 }
184 }
185
186 for i in 0..1024 {
188 let mut word = word_at_index(i);
189 word += "b";
190 let index = index_of_word(&word).unwrap();
191 if index != i {
192 panic!("mismatch");
193 }
194 }
195
196 for i in 0..1024 {
198 let mut word = word_at_index(i);
199 word.truncate(3);
200 word += "a";
201 let index = index_of_word(&word).unwrap();
202 if index != i {
203 panic!("mismatch");
204 }
205 }
206
207 for i in 0..1024 {
209 let mut word = word_at_index(i);
210 word.truncate(3);
211 let index = index_of_word(&word).unwrap();
212 if index != i {
213 panic!("mismatch");
214 }
215 }
216
217 index_of_word("aaron").unwrap_err();
219 index_of_word("ab").unwrap_err();
220
221 assert!(words_match("abbey", "abbot"));
222 assert!(words_match("war", "warp"));
223 assert!(words_match("wolf", "wolf"));
224 assert!(!words_match("ab", "abbey"));
225 assert!(!words_match("aaron", "aaa"));
226 assert!(!words_match("abbey", "warp"));
227 assert!(!words_match("abbey", "aaron"));
228 }
229}