def __init__(self, word, word_set=None): # Word is handled the same way, but word_set needs to be a WordSetHelper. super(WordScrabbleHelper, self).__init__(word, None) # If a word set was given, use it. Else create a new one. if word_set is not None: if not isinstance(word_set, WordSet): raise TypeError("WordSet expected.") # If a WordSet was passed in, upgrade it. if not isinstance(word_set, WordSetHelper): self.word_set = WordSetHelper(word_set()) else: self.word_set = word_set else: self.word_set = WordSet() # The first two searches can be skipped, since we're interested # in suggestions that require tiles we don't have. self.word_set.search_order = self.word_set.search_order[2:]
class WordScrabbleHelper(WordScrabbleCalculator): """ A class combining the WordScrabbleCalculator and WordCorrector to provide improved assistance to Scrabble players. Gives a list of possible words, ordered by their Scrabble scores, as well as some suggestions for words to try go for, also ordered the same way. """ def __init__(self, word, word_set=None): # Word is handled the same way, but word_set needs to be a WordSetHelper. super(WordScrabbleHelper, self).__init__(word, None) # If a word set was given, use it. Else create a new one. if word_set is not None: if not isinstance(word_set, WordSet): raise TypeError("WordSet expected.") # If a WordSet was passed in, upgrade it. if not isinstance(word_set, WordSetHelper): self.word_set = WordSetHelper(word_set()) else: self.word_set = word_set else: self.word_set = WordSet() # The first two searches can be skipped, since we're interested # in suggestions that require tiles we don't have. self.word_set.search_order = self.word_set.search_order[2:] def __repr__(self): print_string = super(WordScrabbleHelper, self).__repr__() new_suggestions = self.get_alternative_suggestions(5) if new_suggestions: print_suggestions = '\n'.join(self._get_suggestion_description(suggestion) for suggestion in new_suggestions) print_string += "\n\nPerhaps you should try going for: \n%s" % (print_suggestions) else: print_string += "\nNo idea what to go for." return print_string def get_alternative_suggestions(self, n): """ Gets a list of words that are worth attempting to get, based on the current word or set of letters. """ # Works best on existing words (the alternatives were originally meant as spelling correctors). # If there are no possible words, use the tiles as they are. possible_words = self.get_possible_words() or [self.word] # There are many alternative words, so use a generator. alternative_words = (alternative for word in possible_words for alternative in self.word_set.get_alternative_words(word)) def limiter(iterator, limit): """A limiter function to stop the generator after a given number of iterations. I love Python.""" for i in xrange(limit): yield next(iterator) raise StopIteration # Suggestions are only valid if they aren't possible, and are within the word length limits. suggestion_valid = lambda x: (x not in possible_words) and (1 < len(x) <= 7) # Put twice the target number in the limiter, in case there are invalid suggestions. suggestions = [word for word in limiter(alternative_words, 10*n) if suggestion_valid(word)] # Return suggestions, ordered by their highest possible Scrabble score value. sorter = lambda x: self._get_suggestion_score(x)[-1] return sorted(suggestions, key=sorter, reverse=True)[:n] def _get_suggestion_description(self, suggestion): """ Get a readable description of the given suggestion. """ # Get information about the suggestion number_of_blanks = self.word.count(' ') letters_needed = self._get_letters_needed(suggestion) score = self._get_suggestion_score(suggestion) description = "- %s. You need" % suggestion # Print letters needed (only a subset of these is required if there are any blanks). if number_of_blanks: description += " %d of" % (len(letters_needed) - number_of_blanks) description += " these letter(s): [%s]" % (','.join(letters_needed)) # Print the score of the word (might be a range if there are any blanks). description += ", for a total score of %d" % (score[0]) if (len(score) > 1) and (score[0] != score[-1]): description += " to %d" % (score[-1]) return description def _get_letters_needed(self, suggestion): """ Get the letters required to make a suggested word. A Counter is used to preserve words containing more than one of the same letter. """ return list((Counter(suggestion) - Counter(self.word)).elements()) def _get_suggestion_score(self, suggestion): """ Get the score of a word suggestion. If the current word contains a blank, this will be a range. """ number_of_blanks = self.word.count(' ') if not number_of_blanks: return (self._get_score(suggestion),) else: # Get the letters (and number thereof) required to make the suggestion. letters_needed = self._get_letters_needed(suggestion) # Subtract the number of blanks from the number of letters needed. number_of_letters_needed = len(letters_needed) - number_of_blanks # Sort the letters by their Scrabble value. values = sorted(letters_needed, key=lambda x: self.letter_values[x]) # Get the least and most valuable letter combos (depending on how many are needed). least_valuable = values[:number_of_letters_needed] most_valuable = values[-number_of_letters_needed:] # Calculate the resulting scores, if these numbered were added. score_without = lambda x: self._get_score(suggestion) - self._get_score(''.join(x)) lower_score, highest_score = score_without(most_valuable), score_without(least_valuable) # Return the result as a tuple. return (lower_score, highest_score)