Skip to content

Instantly share code, notes, and snippets.

@joelburton
Last active February 23, 2017 09:24
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save joelburton/fc580cb7d6d19e989d64a96e1cee1a2a to your computer and use it in GitHub Desktop.
Save joelburton/fc580cb7d6d19e989d64a96e1cee1a2a to your computer and use it in GitHub Desktop.
Hangman variant that sneakily changes the chosen-word to foil the player.
"""Evil Hangman.
Hangman, except avoid choosing a real word and, on a letter guess, reduce
the set of possible words to the set that gives the player as little
advantage as possible.
"""
import random, collections
word_len = int(input("what length word do you want (2-20) [6]") or 6)
with open("/usr/share/dict/words") as words:
words = (w.strip() for w in words)
words = [w.lower() for w in words if w.isalpha() and len(w) == word_len]
nguesses_left = int(input("how many guesses do you want [10] > ") or 10)
guessed_letters = set()
while True:
# cheat: tell us what letters would be best to choose
# print("best letters to choose & counts", collections.Counter(
# c for w in words for c in set(w) - guessed_letters).most_common(3))
ltr = input("\nguess a letter (%d guesses left) > " % nguesses_left).lower()
if len(ltr) != 1:
print("hey, doofus, one letter at a time.")
continue
if ltr in guessed_letters:
print("pay attention, yo. you already guessed that.")
continue
guessed_letters.add(ltr)
# group words in dict by 'family' (key is tuple-of-indices-where-letter-appears)
# for example, if words=[FOO OFF MOO ORE CAT DOG] & ltr=O, our families will be:
# { (1,2): [FOO MOO], (0): [OFF ORE], (1): [DOG], (): [CAT] }
families = collections.defaultdict(list)
for word in words:
indices = [i for i, c in enumerate(word) if ltr==c]
families[tuple(indices)].append(word)
# print("families:", families)
# find #-words of families with longest-list-of-words (in our example above, 2)
longest_fam_len = max(len(f) for f in families.values())
# find all families w/ that word length ( (1,2):[FOO MOO] and (0):[OFF ORE] )
longest_fams = [f for f in families.items() if len(f[1]) == longest_fam_len]
# choose family that uses chosen letter the fewest # of times ([OFF ORE])
fams_with_fewest_match = sorted(longest_fams, key=lambda f: len(f[0]))
fam_with_fewest_match = random.choice(fams_with_fewest_match)
_, words = fam_with_fewest_match
# now words is list of words that are still legal
# print("still legal words:", words)
# to make the next parts easier to read, let's just arbitrarily pick the first
# word from our still-legal words. we're not committing to it; just using it
# so we can check for letter-in-legal-words and to draw-the-success-so-far line.
word = words[0]
if ltr in word:
print("correct")
if not set(word) - guessed_letters: # are there no missing letters?
print("\n\n *** you rock ***\n")
break
else:
print("wrong")
nguesses_left -= 1
if nguesses_left == 0:
print("\n\n *** you suck ***\n")
break
print(" ".join(c if c in guessed_letters else "_" for c in word))
print("guessed", " ".join(sorted(guessed_letters)))
print("word was", word)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment