Hacking Minigame

Tags: large, artistic, game, puzzle

In this game, the player must hack a computer by guessing a seven-letter word used as the secret password. The computer’s memory banks display the possible words, and the player is given hints as to how close each guess was. For example, if the secret password is MONITOR but the player guessed CONTAIN, they are given the hint that two out of seven letters were correct, because both MONITOR and CONTAIN have the letter O and N as their second and third letter. This game is similar to Project 1, “Bagels,” and the hacking minigame in the Fallout series of video games.

hacking_minigame.py
  1"""Hacking Minigame, by Al Sweigart al@inventwithpython.com
  2The hacking mini-game from "Fallout 3". Find out which seven-letter
  3word is the password by using clues each guess gives you.
  4This code is available at https://nostarch.com/big-book-small-python-programming
  5Tags: large, artistic, game, puzzle"""
  6
  7# NOTE: This program requires the sevenletterwords.txt file. You can
  8# download it from https://inventwithpython.com/sevenletterwords.txt
  9
 10import random, sys
 11
 12# Set up the constants:
 13# The garbage filler characters for the "computer memory" display.
 14GARBAGE_CHARS = '~!@#$%^&*()_+-={}[]|;:,.<>?/'
 15
 16# Load the WORDS list from a text file that has 7-letter words.
 17with open('sevenletterwords.txt') as wordListFile:
 18    WORDS = wordListFile.readlines()
 19for i in range(len(WORDS)):
 20    # Convert each word to uppercase and remove the trailing newline:
 21    WORDS[i] = WORDS[i].strip().upper()
 22
 23
 24def main():
 25    """Run a single game of Hacking."""
 26    print('''Hacking Minigame, by Al Sweigart al@inventwithpython.com
 27Find the password in the computer's memory. You are given clues after
 28each guess. For example, if the secret password is MONITOR but the
 29player guessed CONTAIN, they are given the hint that 2 out of 7 letters
 30were correct, because both MONITOR and CONTAIN have the letter O and N
 31as their 2nd and 3rd letter. You get four guesses.\n''')
 32    input('Press Enter to begin...')
 33
 34    gameWords = getWords()
 35    # The "computer memory" is just cosmetic, but it looks cool:
 36    computerMemory = getComputerMemoryString(gameWords)
 37    secretPassword = random.choice(gameWords)
 38
 39    print(computerMemory)
 40    # Start at 4 tries remaining, going down:
 41    for triesRemaining in range(4, 0, -1):
 42        playerMove = askForPlayerGuess(gameWords, triesRemaining)
 43        if playerMove == secretPassword:
 44            print('A C C E S S   G R A N T E D')
 45            return
 46        else:
 47            numMatches = numMatchingLetters(secretPassword, playerMove)
 48            print('Access Denied ({}/7 correct)'.format(numMatches))
 49    print('Out of tries. Secret password was {}.'.format(secretPassword))
 50
 51
 52def getWords():
 53    """Return a list of 12 words that could possibly be the password.
 54
 55    The secret password will be the first word in the list.
 56    To make the game fair, we try to ensure that there are words with
 57    a range of matching numbers of letters as the secret word."""
 58    secretPassword = random.choice(WORDS)
 59    words = [secretPassword]
 60
 61    # Find two more words; these have zero matching letters.
 62    # We use "< 3" because the secret password is already in words.
 63    while len(words) < 3:
 64        randomWord = getOneWordExcept(words)
 65        if numMatchingLetters(secretPassword, randomWord) == 0:
 66            words.append(randomWord)
 67
 68    # Find two words that have 3 matching letters (but give up at 500
 69    # tries if not enough can be found).
 70    for i in range(500):
 71        if len(words) == 5:
 72            break  # Found 5 words, so break out of the loop.
 73
 74        randomWord = getOneWordExcept(words)
 75        if numMatchingLetters(secretPassword, randomWord) == 3:
 76            words.append(randomWord)
 77
 78    # Find at least seven words that have at least one matching letter
 79    # (but give up at 500 tries if not enough can be found).
 80    for i in range(500):
 81        if len(words) == 12:
 82            break  # Found 7 or more words, so break out of the loop.
 83
 84        randomWord = getOneWordExcept(words)
 85        if numMatchingLetters(secretPassword, randomWord) != 0:
 86            words.append(randomWord)
 87
 88    # Add any random words needed to get 12 words total.
 89    while len(words) < 12:
 90        randomWord = getOneWordExcept(words)
 91        words.append(randomWord)
 92
 93    assert len(words) == 12
 94    return words
 95
 96
 97def getOneWordExcept(blocklist=None):
 98    """Returns a random word from WORDS that isn't in blocklist."""
 99    if blocklist == None:
100        blocklist = []
101
102    while True:
103        randomWord = random.choice(WORDS)
104        if randomWord not in blocklist:
105            return randomWord
106
107
108def numMatchingLetters(word1, word2):
109    """Returns the number of matching letters in these two words."""
110    matches = 0
111    for i in range(len(word1)):
112        if word1[i] == word2[i]:
113            matches += 1
114    return matches
115
116
117def getComputerMemoryString(words):
118    """Return a string representing the "computer memory"."""
119
120    # Pick one line per word to contain a word. There are 16 lines, but
121    # they are split into two halves.
122    linesWithWords = random.sample(range(16 * 2), len(words))
123    # The starting memory address (this is also cosmetic).
124    memoryAddress = 16 * random.randint(0, 4000)
125
126    # Create the "computer memory" string.
127    computerMemory = []  # Will contain 16 strings, one for each line.
128    nextWord = 0  # The index in words of the word to put into a line.
129    for lineNum in range(16):  # The "computer memory" has 16 lines.
130        # Create a half line of garbage characters:
131        leftHalf = ''
132        rightHalf = ''
133        for j in range(16):  # Each half line has 16 characters.
134            leftHalf += random.choice(GARBAGE_CHARS)
135            rightHalf += random.choice(GARBAGE_CHARS)
136
137        # Fill in the password from words:
138        if lineNum in linesWithWords:
139            # Find a random place in the half line to insert the word:
140            insertionIndex = random.randint(0, 9)
141            # Insert the word:
142            leftHalf = (leftHalf[:insertionIndex] + words[nextWord]
143                + leftHalf[insertionIndex + 7:])
144            nextWord += 1  # Update the word to put in the half line.
145        if lineNum + 16 in linesWithWords:
146            # Find a random place in the half line to insert the word:
147            insertionIndex = random.randint(0, 9)
148            # Insert the word:
149            rightHalf = (rightHalf[:insertionIndex] + words[nextWord]
150                + rightHalf[insertionIndex + 7:])
151            nextWord += 1  # Update the word to put in the half line.
152
153        computerMemory.append('0x' + hex(memoryAddress)[2:].zfill(4)
154                     + '  ' + leftHalf + '    '
155                     + '0x' + hex(memoryAddress + (16*16))[2:].zfill(4)
156                     + '  ' + rightHalf)
157
158        memoryAddress += 16  # Jump from, say, 0xe680 to 0xe690.
159
160    # Each string in the computerMemory list is joined into one large
161    # string to return:
162    return '\n'.join(computerMemory)
163
164
165def askForPlayerGuess(words, tries):
166    """Let the player enter a password guess."""
167    while True:
168        print('Enter password: ({} tries remaining)'.format(tries))
169        guess = input('> ').upper()
170        if guess in words:
171            return guess
172        print('That is not one of the possible passwords listed above.')
173        print('Try entering "{}" or "{}".'.format(words[0], words[1]))
174
175
176# If this program was run (instead of imported), run the game:
177if __name__ == '__main__':
178    try:
179        main()
180    except KeyboardInterrupt:
181        sys.exit()  # When Ctrl-C is pressed, end the program.

https://inventwithpython.com/bigbookpython/project33.html