-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmain.go
197 lines (167 loc) · 6.79 KB
/
main.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
// Welcome to the BeeSolver! This nifty package is your secret weapon for tackling
// the New York Times Spelling Bee puzzles. It's not just about finding words; it's
// about embarking on a lexical adventure. With functions to sift through word lists
// and conjure up solutions, BeeSolver transforms a jumble of letters into a celebration
// of words. Whether you're a puzzle enthusiast or a word wizard in the making,
// this is where your journey with letters begins. Let's dive into the hive!
package main
import (
"bufio"
"fmt"
"os"
"sort"
"strings"
"unicode"
)
func main() {
var requiredLetter, otherLetters string
var validArgs bool
// Ah, command line arguments! For the user who comes prepared with letters in hand. But do they have enough?
if len(os.Args) > 1 && len(os.Args) < 3 {
// Seems like we're a bit short on arguments. Counting to 3 is crucial, just like knowing the alphabet!
fmt.Fprintln(os.Stderr, "Missing arguments. Please provide both the required letter and the six other letters.")
os.Exit(1)
}
// Aha! Exactly three arguments, just like Goldilocks' porridge – this might be just right.
if len(os.Args) == 3 {
cmdRequiredLetter := os.Args[1]
cmdOtherLetters := os.Args[2]
// Let's see if these letters pass the test. We appreciate effort, but we need accuracy!
if isValidInput(cmdRequiredLetter, 1, "") && isValidInput(cmdOtherLetters, 6, cmdRequiredLetter) {
requiredLetter = strings.ToUpper(cmdRequiredLetter)
otherLetters = strings.ToUpper(cmdOtherLetters)
validArgs = true
} else {
// Oops! Those letters didn't make the cut. Let's tell them gently but firmly.
fmt.Fprintln(os.Stderr, "Invalid command line arguments. We're looking for one required letter and six others, no more, no less.")
os.Exit(1)
}
}
// No command line arguments? No problem! Let's ask the user nicely for the letters.
if !validArgs {
requiredLetter = getValidInput("Enter the required letter (one letter only): ", 1, "")
otherLetters = getValidInput("Enter the other letters (six letters, no duplicates): ", 6, requiredLetter)
}
// Load words from the file
words, err := loadWords("assets/possible_answers.txt")
if err != nil {
fmt.Println("Error loading words:", err)
return
}
// Generate a map from the list of words where each key is a word from the list and its corresponding value
// is a string of the unique letters of that word, sorted alphabetically. This map is used for efficiently
// checking whether a given combination of letters forms a valid word in the filtered word list.
wordMap := generateMap(words)
// Find valid words using the input letters. This function filters the words from the wordMap to find those
// that include the required letter and are composed exclusively of the combined set of the required and
// other optional letters. The resulting list, validWords, contains all the words that meet the New York Times
// Spelling Bee puzzle criteria and is sorted alphabetically.
validWords := findValidWords(requiredLetter, otherLetters, wordMap)
// TODO: Sprinkle some magic here? Group words by their starting letter?
for _, word := range validWords {
fmt.Println(word)
}
}
// loadWords loads words from a given file.
func loadWords(filePath string) ([]string, error) {
file, err := os.Open(filePath)
if err != nil {
return nil, err
}
defer file.Close()
var words []string
scanner := bufio.NewScanner(file)
for scanner.Scan() {
words = append(words, strings.ToUpper(scanner.Text()))
}
if err := scanner.Err(); err != nil {
return nil, err
}
return words, nil
}
// generateMap generates a map of words to their sorted unique letters.
func generateMap(words []string) map[string]string {
wordMap := make(map[string]string)
for _, word := range words {
wordMap[word] = sortUniqueLetters(word)
}
return wordMap
}
// sortUniqueLetters returns a string with alphabetically sorted unique letters of the given word.
func sortUniqueLetters(word string) string {
letterMap := make(map[rune]bool)
for _, letter := range word {
letterMap[letter] = true
}
var uniqueLetters []rune
for letter := range letterMap {
uniqueLetters = append(uniqueLetters, letter)
}
sort.Slice(uniqueLetters, func(i, j int) bool {
return uniqueLetters[i] < uniqueLetters[j]
})
return string(uniqueLetters)
}
// getValidInput prompts the user for input based on the provided prompt message. It validates the input to ensure
// it adheres to the rules of the Spelling Bee game: the input must only contain English letters (A-Z),
// must meet the specified length criteria (one letter for the required letter, six for the other letters), and
// must not include any characters specified in the 'exclude' parameter (to prevent duplication of the required letter
// in the list of other letters). The function repeatedly prompts the user until valid input is provided, enforcing
// the game's input rules for letter selection.
func getValidInput(prompt string, length int, exclude string) string {
reader := bufio.NewReader(os.Stdin)
for {
fmt.Print(prompt)
input, _ := reader.ReadString('\n')
input = strings.ToUpper(strings.TrimSpace(input))
if isValidInput(input, length, exclude) {
return strings.ToUpper(input)
}
fmt.Println("Invalid input. Please try again.")
}
}
// isValidInput checks if the input is valid based on length, characters, and exclusion criteria.
func isValidInput(input string, length int, exclude string) bool {
if len(input) != length {
return false
}
charMap := make(map[rune]bool)
for _, char := range input {
if !unicode.IsLetter(char) || strings.ContainsRune(exclude, char) {
return false
}
if charMap[char] {
return false // Duplicate character found
}
charMap[char] = true
}
return true
}
// findValidWords finds and returns words that contain the required letter and are made only of the allowed letters.
func findValidWords(requiredLetter, otherLetters string, wordMap map[string]string) []string {
var validWords []string
allowedLetters := requiredLetter + otherLetters
for word, sortedUniqueLetters := range wordMap {
if contains(sortedUniqueLetters, requiredLetter) && onlyContains(sortedUniqueLetters, allowedLetters) {
validWords = append(validWords, word)
}
}
sort.Strings(validWords) // I like my word lists alphabetical.
return validWords
}
// contains checks if the string 's' contains the substring 'sub'.
// 'sub' is the letter that Spelling Bee requires for a word to be a valid answer.
func contains(s, sub string) bool {
return strings.Contains(s, sub)
}
// onlyContains checks if the string 's' contains only the characters in 'allowed'.
// words are only valid answers in Spelling Bee if they only use a combination of
// the required and optional letters.
func onlyContains(s, allowed string) bool {
for _, char := range s {
if !strings.ContainsRune(allowed, char) {
return false
}
}
return true
}