Advertisement
Guest User

Untitled

a guest
Feb 24th, 2020
78
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 11.87 KB | None | 0 0
  1. # 6.0001 Problem Set 3
  2. #
  3. # The 6.0001 Word Game
  4. # Created by: Kevin Luu <luuk> and Jenna Wiens <jwiens>
  5. #
  6. # Name          : <your name>
  7. # Collaborators : <your collaborators>
  8. # Time spent    : <total time>
  9.  
  10. import math
  11. import random
  12. import cProfile
  13.  
  14. VOWELS = ['a', 'e', 'i', 'o', 'u']
  15. CONSONANTS = 'bcdfghjklmnpqrstvwxyz'
  16. HAND_SIZE = 7
  17.  
  18. SCRABBLE_LETTER_VALUES = {
  19.     'a': 1, 'b': 3, 'c': 3, 'd': 2, 'e': 1, 'f': 4, 'g': 2, 'h': 4, 'i': 1, 'j': 8, 'k': 5, 'l': 1, 'm': 3, 'n': 1,
  20.     'o': 1, 'p': 3, 'q': 10, 'r': 1, 's': 1, 't': 1, 'u': 1, 'v': 4, 'w': 4, 'x': 8, 'y': 4, 'z': 10, '*': 0
  21. }
  22.  
  23.  
  24. # -----------------------------------
  25. # Helper code
  26. # (you don't need to understand this helper code)
  27.  
  28. WORDLIST_FILENAME = "words.txt"
  29.  
  30.  
  31. def load_words():
  32.     """
  33.    Returns a list of valid words. Words are strings of lowercase letters.
  34.  
  35.    Depending on the size of the word list, this function may
  36.    take a while to finish.
  37.    """
  38.  
  39.     print("Loading word list from file...")
  40.     # inFile: file
  41.     inFile = open(WORDLIST_FILENAME, 'r')
  42.     # wordlist: list of strings
  43.     wordlist = []
  44.     for line in inFile:
  45.         wordlist.append(line.strip().lower())
  46.     print(f"{len(wordlist)} words loaded.")
  47.     return wordlist
  48.  
  49.  
  50. def get_frequency_dict(sequence):
  51.     """
  52.    Returns a dictionary where the keys are elements of the sequence
  53.    and the values are integer counts, for the number of times that
  54.    an element is repeated in the sequence.
  55.  
  56.    sequence: string or list
  57.    return: dictionary
  58.    """
  59.     if sequence in get_frequency_dict.cache:
  60.         return get_frequency_dict.cache[sequence]
  61.  
  62.     # freqs: dictionary (element_type -> int)
  63.     freq = {}
  64.     for x in sequence:
  65.         freq[x] = freq.get(x, 0) + 1
  66.     get_frequency_dict.cache[sequence] = freq
  67.     return freq
  68. get_frequency_dict.cache = {}
  69.  
  70. # (end of helper code)
  71. # -----------------------------------
  72.  
  73. #
  74. # Problem #1: Scoring a word
  75. #
  76. def get_word_score(word, n):
  77.     """
  78.    Returns the score for a word. Assumes the word is a
  79.    valid word.
  80.  
  81.    You may assume that the input word is always either a string of letters,
  82.    or the empty string "". You may not assume that the string will only contain
  83.    lowercase letters, so you will have to handle uppercase and mixed case strings
  84.    appropriately.
  85.  
  86.     The score for a word is the product of two components:
  87.  
  88.     The first component is the sum of the points for letters in the word.
  89.     The second component is the larger of:
  90.            1, or
  91.            7*wordlen - 3*(n-wordlen), where wordlen is the length of the word
  92.            and n is the hand length when the word was played
  93.  
  94.     Letters are scored as in Scrabble; A is worth 1, B is
  95.     worth 3, C is worth 3, D is worth 2, E is worth 1, and so on.
  96.  
  97.    word: string
  98.    n: int >= 0
  99.    returns: int >= 0
  100.    """
  101.     score_a = 0
  102.     for c in word.lower():
  103.         score_a += SCRABBLE_LETTER_VALUES[c]
  104.     score_b = max(1, 7 * len(word) - 3 * (n - len(word)))
  105.     return score_a * score_b
  106.  
  107.  
  108. #
  109. # Make sure you understand how this function works and what it does!
  110. #
  111. def display_hand(hand):
  112.     """
  113.    Displays the letters currently in the hand.
  114.  
  115.    For example:
  116.       display_hand({'a':1, 'x':2, 'l':3, 'e':1})
  117.    Should print out something like:
  118.       a x x l l l e
  119.    The order of the letters is unimportant.
  120.  
  121.    hand: dictionary (string -> int)
  122.    """
  123.  
  124.     for letter in hand.keys():
  125.         for j in range(hand[letter]):
  126.             print(letter, end=' ')  # print all on the same line
  127.     print()  # print an empty line
  128.  
  129.  
  130. #
  131. # Make sure you understand how this function works and what it does!
  132. # You will need to modify this for Problem #4.
  133. #
  134. def deal_hand(n):
  135.     """
  136.    Returns a random hand containing n lowercase letters.
  137.    ceil(n/3) letters in the hand should be VOWELS (note,
  138.    ceil(n/3) means the smallest integer not less than n/3).
  139.  
  140.    Hands are represented as dictionaries. The keys are
  141.    letters and the values are the number of times the
  142.    particular letter is repeated in that hand.
  143.  
  144.    n: int >= 0
  145.    returns: dictionary (string -> int)
  146.    """
  147.  
  148.     hand = {}
  149.     num_vowels = int(math.ceil(n / 3)) - 1
  150.  
  151.     for i in range(num_vowels):
  152.         x = random.choice(VOWELS)
  153.         hand[x] = hand.get(x, 0) + 1
  154.  
  155.     for i in range(num_vowels, n):
  156.         x = random.choice(CONSONANTS)
  157.         hand[x] = hand.get(x, 0) + 1
  158.  
  159.     hand['*'] = 1
  160.  
  161.     return hand
  162.  
  163.  
  164. #
  165. # Problem #2: Update a hand by removing letters
  166. #
  167. def update_hand(hand, word):
  168.     """
  169.    Does NOT assume that hand contains every letter in word at least as
  170.    many times as the letter appears in word. Letters in word that don't
  171.    appear in hand should be ignored. Letters that appear in word more times
  172.    than in hand should never result in a negative count; instead, set the
  173.    count in the returned hand to 0 (or remove the letter from the
  174.    dictionary, depending on how your code is structured).
  175.  
  176.    Updates the hand: uses up the letters in the given word
  177.    and returns the new hand, without those letters in it.
  178.  
  179.    Has no side effects: does not modify hand.
  180.  
  181.    word: string
  182.    hand: dictionary (string -> int)
  183.    returns: dictionary (string -> int)
  184.    """
  185.     new_hand = hand.copy()
  186.  
  187.     for c in word.lower():
  188.         if c in new_hand:
  189.             new_hand[c] = max(0, new_hand[c] - 1)
  190.  
  191.     return new_hand
  192.  
  193.  
  194. #
  195. # Problem #3: Test word validity
  196. #
  197. def is_valid_word(word, hand, handlen=None):
  198.     """ Checks that word is composable of letters/wildcards in hand"""
  199.  
  200.     # optimization
  201.     if handlen == None:
  202.         handlen = calculate_handlen(hand)
  203.     if len(word) > handlen:
  204.         return False
  205.  
  206.     word_freq = get_frequency_dict(word)
  207.     wildcards = hand.get('*', 0)
  208.  
  209.     for c, count in word_freq.items():
  210.         if hand.get(c, -1) < count:
  211.             if c in VOWELS:
  212.                 wildcards -= 1
  213.                 if wildcards < 0:
  214.                     return False
  215.             return False
  216.     return True
  217.  
  218.  
  219. #
  220. # Problem #5: Playing a hand
  221. #
  222. def calculate_handlen(hand):
  223.     """
  224.    Returns the length (number of letters) in the current hand.
  225.  
  226.    hand: dictionary (string-> int)
  227.    returns: integer
  228.    """
  229.     return sum(hand.values())
  230.  
  231.  
  232. def play_hand(hand, word_list):
  233.     tree = {}
  234.     handlen = calculate_handlen(hand)
  235.  
  236.     permutate_hand(hand, word_list, tree)
  237.     best_seq, best_score = get_best_seq(tree, handlen)
  238.     print("="*40)
  239.     print("These are the words the computer figured out:")
  240.     for word in best_seq:
  241.         print(f"{word} scored {get_word_score(word, handlen)} points!")
  242.  
  243.     print(f"Total score for this hand was: {best_score}")
  244.     print("="*40)
  245.  
  246.     return best_score
  247.  
  248. def permutate_hand(hand, word_list, tree):
  249.     handlen = calculate_handlen(hand)
  250.     for word in word_list:
  251.         if is_valid_word(word, hand, handlen):
  252.             word_hand = update_hand(hand, word)
  253.             tree[word] = {}
  254.             permutate_hand(word_hand, word_list, tree[word])
  255.  
  256. def get_best_seq(tree, handlen):
  257.     best_seq = []
  258.     best_score = -1
  259.  
  260.     for key, subtree in tree.items():
  261.         seq = [key]
  262.         score = get_word_score(key, handlen)
  263.         if subtree:
  264.             subseq, subscore = get_best_seq(subtree, handlen)
  265.             seq += subseq
  266.             score += subscore
  267.         if score > best_score:
  268.             best_seq = seq
  269.             best_score = score
  270.  
  271.     return best_seq, best_score
  272.  
  273. #
  274. # Problem #6: Playing a game
  275. #
  276.  
  277.  
  278. #
  279. # procedure you will use to substitute a letter in a hand
  280. #
  281.  
  282. def substitute_hand(hand, letter):
  283.     """
  284.    Allow the user to replace all copies of one letter in the hand (chosen by user)
  285.    with a new letter chosen from the VOWELS and CONSONANTS at random. The new letter
  286.    should be different from user's choice, and should not be any of the letters
  287.    already in the hand.
  288.  
  289.    If user provide a letter not in the hand, the hand should be the same.
  290.  
  291.    Has no side effects: does not mutate hand.
  292.  
  293.    For example:
  294.        substitute_hand({'h':1, 'e':1, 'l':2, 'o':1}, 'l')
  295.    might return:
  296.        {'h':1, 'e':1, 'o':1, 'x':2} -> if the new letter is 'x'
  297.    The new letter should not be 'h', 'e', 'l', or 'o' since those letters were
  298.    already in the hand.
  299.  
  300.    hand: dictionary (string -> int)
  301.    letter: string
  302.    returns: dictionary (string -> int)
  303.    """
  304.     local_hand = hand.copy()
  305.  
  306.     if letter not in local_hand:
  307.         return local_hand
  308.  
  309.     possible_letters = list(set(SCRABBLE_LETTER_VALUES.keys()) - set(hand.keys()))
  310.     count = local_hand[letter]
  311.     del local_hand[letter]
  312.  
  313.     local_hand[random.choice(possible_letters)] = count
  314.  
  315.     return local_hand
  316.  
  317.  
  318. def ask_int(prompt):
  319.     while True:
  320.         value = input(prompt)
  321.         try:
  322.             return int(value)
  323.         except:
  324.             print("Enter a number, idiot!")
  325.  
  326.  
  327. def ask_letter(prompt):
  328.     while True:
  329.         value = input(prompt)
  330.         if value in SCRABBLE_LETTER_VALUES.keys():
  331.             return value
  332.         print('Not a scrabble letter, bakkkaaaa')
  333.  
  334.  
  335. def ask_yesno(prompt):
  336.     while True:
  337.         value = input(prompt)
  338.         if value.lower() == 'yes':
  339.             return True
  340.         if value.lower() == 'no':
  341.             return False
  342.         print('Answer yes/no, aho')
  343.  
  344.  
  345. def play_game(word_list):
  346.     """
  347.    Allow the user to play a series of hands
  348.  
  349.    * Asks the user to input a total number of hands
  350.  
  351.    * Accumulates the score for each hand into a total score for the
  352.      entire series
  353.  
  354.    * For each hand, before playing, ask the user if they want to substitute
  355.      one letter for another. If the user inputs 'yes', prompt them for their
  356.      desired letter. This can only be done once during the game. Once the
  357.      substitue option is used, the user should not be asked if they want to
  358.      substitute letters in the future.
  359.  
  360.    * For each hand, ask the user if they would like to replay the hand.
  361.      If the user inputs 'yes', they will replay the hand and keep
  362.      the better of the two scores for that hand.  This can only be done once
  363.      during the game. Once the replay option is used, the user should not
  364.      be asked if they want to replay future hands. Replaying the hand does
  365.      not count as one of the total number of hands the user initially
  366.      wanted to play.
  367.  
  368.            * Note: if you replay a hand, you do not get the option to substitute
  369.                    a letter - you must play whatever hand you just had.
  370.  
  371.    * Returns the total score for the series of hands
  372.  
  373.    word_list: list of lowercase strings
  374.    """
  375.     hands = ask_int("How many games do you want the computer to play for you?")
  376.     letters_per_hand = ask_int("How many letters should the computer play per hand?")
  377.     score = 0
  378.     can_substitute = True
  379.  
  380.     for i in range(hands):
  381.         hand = deal_hand(letters_per_hand)
  382.         print(f"This is the hand the computer {'wants' if can_substitute else 'will'} play: ")
  383.         display_hand(hand)
  384.  
  385.         if can_substitute:
  386.             substitute = ask_yesno('Would you like to trick the computer by substituting a character?')
  387.             if substitute:
  388.                 can_substitute = False
  389.                 letter = ask_letter('Nice trick! Which letter?')
  390.                 hand = substitute_hand(hand, letter)
  391.                 print("Here is your tricky new hand, the computer will be stumped!")
  392.                 display_hand(hand)
  393.  
  394.         score += play_hand(hand, word_list)
  395.         print(f"Nice, the computer has scored a total of {score} points!")
  396.  
  397.  
  398. #
  399. # Build data structures used for entire session and play game
  400. # Do not remove the "if __name__ == '__main__':" line - this code is executed
  401. # when the program is run directly, instead of through an import statement
  402. #
  403. if __name__ == '__main__':
  404.     word_list = load_words()
  405.     cProfile.run('play_game(word_list)')
  406.     #play_game(word_list)
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement