|
from random import randint, sample |
|
from utils import product, random_copies, random_primes |
|
|
|
|
|
class Game: |
|
"""A game of Prime Hangman.""" |
|
|
|
def __init__(self, start, end, num_primes, num_copies, num_choices, |
|
num_lives): |
|
"""Initialize the game. |
|
|
|
Args: |
|
start (int): the lower bound for possible primes. |
|
end (int): the upper bound for possible primes. |
|
num_primes (int): the number of random primes to generate. |
|
max_copies (int): the maximum number of copies of generated primes. |
|
num_choices (int): the number of choices of primes that the user begins with. |
|
num_lives (int): the number of lives available before the game ends without victory. |
|
""" |
|
self.num_copies = num_copies |
|
|
|
self.choices = random_primes(start, end, num_choices) |
|
self.primes = sample(self.choices, num_primes) |
|
self.factors = self.primes + random_copies(self.primes, self.num_copies) |
|
self.composite = product(self.factors) |
|
|
|
self.guess = '' |
|
self.guesses = [] |
|
self.num_guesses = 0 |
|
self.num_correct = 0 |
|
self.num_lives = num_lives |
|
|
|
self.blank = '___' |
|
self.horizontal = "------------------------------------------------------------" |
|
|
|
self.victory = False |
|
self.in_progress = True |
|
|
|
def __str__(self): |
|
return ' * '.join([str(f) for f in sorted(self.factors)]) + ' = ' + str( |
|
self.composite) |
|
|
|
def reduced_composite(self): |
|
"""Divides the composite number by each of its guessed factors.""" |
|
reduced = self.composite * 1 |
|
for factor in self.factors: |
|
if factor in self.guesses: |
|
reduced = reduced / factor |
|
return int(reduced) |
|
|
|
def reduced_expression(self): |
|
"""A multiplication expression with the reduced composite and the guessed factors""" |
|
r = [self.reduced_composite()] |
|
for guess in self.guesses: |
|
if guess in self.factors: |
|
for _ in range(self.factors.count(guess)): |
|
r.append(guess) |
|
return ' * '.join([str(n) for n in r]) |
|
|
|
def are_within_one(self, a, b): |
|
"""Determines if two numbers are within one of each other""" |
|
if a == b or a-1 == b or a+1 == b: |
|
return True |
|
else: |
|
return False |
|
|
|
def puzzle(self): |
|
"""The puzzle as a string to be displayed to the user.""" |
|
p = [] |
|
for factor in self.factors: |
|
if factor not in self.guesses: |
|
p.append(self.blank) |
|
else: |
|
p.append(factor) |
|
if self.are_within_one(self.reduced_composite(), self.composite) or self.reduced_composite( |
|
) in self.factors: |
|
r = '' |
|
else: |
|
r = '(' + self.reduced_expression() + ')' |
|
return ' * '.join([str(n) for n in p]) + ' = ' + str( |
|
self.composite) + ' ' + r |
|
|
|
def message(self): |
|
"""Message to display before each guess""" |
|
str_choices = ', '.join([str(c) for c in self.choices]) |
|
lives_str = "life left" if self.num_lives == 1 else "lives left" |
|
m = self.horizontal + "\n" \ |
|
"Puzzle ({0} {1}):" \ |
|
"\n\n{2}\n\n" \ |
|
"{3} choices: " \ |
|
"{4}" \ |
|
.format(self.num_lives, lives_str, self.puzzle(), len(self.choices), str_choices) |
|
print(m) |
|
|
|
def getInput(self): |
|
"""Retrieves and directs user input.""" |
|
print(self.horizontal) |
|
raw = input("Make a guess (Q to Quit): ") |
|
if raw.isdigit(): |
|
guess = int(raw) |
|
if guess not in self.choices: |
|
print('{0} is not a valid guess.'.format(guess)) |
|
else: |
|
self.guess = guess |
|
|
|
elif raw.lower() == 'q': |
|
self.in_progress = False |
|
|
|
else: |
|
print('{0} is not a valid guess.'.format(raw)) |
|
|
|
def removeChoice(self, guess): |
|
"""Removes a guessed choice from the list of choices.""" |
|
self.choices = [choice for choice in self.choices if choice != guess] |
|
|
|
def makeGuess(self): |
|
"""Adds current guess to the guess list and removes it as a choice.""" |
|
|
|
if self.guess in self.choices: |
|
self.guesses.append(self.guess) |
|
self.removeChoice(self.guess) |
|
self.num_guesses += 1 |
|
|
|
if self.guess in self.factors: |
|
print() |
|
print(str(self.guess) + ' is in the solution!') |
|
self.num_correct += 1 |
|
else: |
|
self.num_lives -= 1 |
|
print() |
|
print(str(self.guess) + ' is not in the solution.') |
|
print("You've lost a life!") |
|
|
|
def checkOutcome(self): |
|
"""Determines if the game should end and if victory has been achieved.""" |
|
if len(self.choices) == 0 or self.num_lives <= 0: |
|
self.in_progress = False |
|
|
|
if all(factor in self.guesses for factor in self.factors): |
|
self.victory = True |
|
self.in_progress = False |
|
|
|
def calculate_score(self): |
|
"""Calculates the player's score for the game.""" |
|
num_incorrect = self.num_guesses - self.num_correct |
|
return int(self.num_correct * 100 / (len(set(self.factors)) + num_incorrect)) |
|
|
|
def endMessage(self): |
|
"""Message to display after the game ends.""" |
|
if self.victory: |
|
print('You won with a score of {0} / 100!'.format(self.calculate_score())) |
|
print(self) |
|
|
|
else: |
|
print('Game over.', end=' ') |
|
|
|
if self.num_lives <= 0: |
|
print('You ran out of lives with a score of {0} / 100.'.format(self.calculate_score())) |
|
else: |
|
print("Your score was {0} / 100".format(self.calculate_score())) |
|
|
|
print('The solution was: ', end='') |
|
print(self) |
|
print() |
|
|
|
def play(self): |
|
"""Allows the user to begin playing the game.""" |
|
while self.in_progress: |
|
self.message() |
|
self.getInput() |
|
self.makeGuess() |
|
self.checkOutcome() |
|
|
|
print() |
|
self.endMessage() |