Skip to content

Instantly share code, notes, and snippets.

@sequentialchaos
Last active January 1, 2020 08:05
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save sequentialchaos/d0a69332f80e9c59cbe85dcc22ab6594 to your computer and use it in GitHub Desktop.
Save sequentialchaos/d0a69332f80e9c59cbe85dcc22ab6594 to your computer and use it in GitHub Desktop.
Prime Hangman - a CLI math game

Prime Hangman

Introduction

Have you ever wanted to play the game Hangman without words or letters, but with prime numbers and digits? Well, now's your chance!


Game Instructions

Prime Hangman starts you off with one composite number and a list of prime numbers (or just "primes"). Some of these primes are factors of the composite number, the others are not. Your goal is to determine the prime factorization of the composite number by choosing from the list of primes, one at a time. Each incorrect guess costs you a life, and if you run out of lives, then the game ends. So choose carefully!

There are 8 difficulty modes, each with a different range of primes and number of prime factors:

  1. Very Easy (start here!)
  2. Easy
  3. Normal
  4. Hard
  5. Very Hard
  6. Nightmare
  7. Impossible
  8. Random

Getting started

  1. Make sure that you have python3 installed.
  2. Download and unzip this gist.
  3. Open a terminal / command prompt and go to the game's directory.
  4. Enter the command python3 play.py and then play!
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()
from game import Game
from utils import prime_sieve_range
from random import randint
def veryEasyGame():
return Game(
start=2,
end=17,
num_primes=2,
num_copies=0,
num_choices=6,
num_lives=3)
def easyGame():
return Game(
start=5,
end=31,
num_primes=2,
num_copies=0,
num_choices=7,
num_lives=4)
def normalGame():
return Game(
start=3,
end=71,
num_primes=2,
num_copies=1,
num_choices=7,
num_lives=4)
def hardGame():
return Game(
start=7,
end=97,
num_primes=3,
num_copies=0,
num_choices=7,
num_lives=3)
def veryHardGame():
return Game(
start=11,
end=97,
num_primes=3,
num_copies=1,
num_choices=8,
num_lives=4)
def nightmareGame():
return Game(
start=13,
end=130,
num_primes=4,
num_copies=2,
num_choices=11,
num_lives=5)
def impossibleGame():
return Game(
start=31,
end=997,
num_primes=6,
num_copies=3,
num_choices=11,
num_lives=3)
def randomGame():
_start = randint(2, 37)
range_length = randint(300, 500)
_end = randint(_start, _start + range_length)
max_primes = len(prime_sieve_range(_start, _end))
_num_primes = randint(2, 6)
if _num_primes > max_primes:
_num_primes = max_primes - 2
_num_copies = randint(0, 5)
_num_choices = randint(_num_primes + 3, _num_primes + 6)
_num_lives = randint(_num_choices - _num_primes - 1, _num_choices - _num_primes)
return Game(
start=_start,
end=_end,
num_primes=_num_primes,
num_copies=_num_copies,
num_choices=_num_choices,
num_lives=_num_lives
)
modes_list = ['Very Easy', 'Easy', 'Normal', 'Hard', 'Very Hard', 'Nightmare', 'Impossible', 'Random ;)']
#!/usr/bin/python3
from modes import veryEasyGame, easyGame, normalGame, \
hardGame, veryHardGame, nightmareGame, \
impossibleGame, randomGame, modes_list
def showIntro():
print("|------------------------------------------------------------|\n" \
"| Welcome to Prime Hangman! |\n" \
"|------------------------------------------------------------|\n" \
"| Your objective is to determine the *prime factorization* |\n" \
"| of the displayed composite number by choosing from a list |\n" \
"| of primes, one at a time. |\n" \
"| |\n" \
"| Every incorrect guess costs you a life, and if you run |\n" \
"| out of lives, the game ends. So choose carefully! |\n" \
"| |\n" \
"| Each unknown prime factor will be represented by one |\n" \
"| or more blanks, which look like -> '___'. |\n" \
"|------------------------------------------------------------|\n")
def havePermission(round_number):
translation = '?'
while translation != True and translation != False:
if round_number == 1:
response = input("Would you like to play? (y/n): ")
else:
response = input("Would you like to play again? (y/n): ")
if response == 'y' or response == 'Y':
translation = True
elif response == 'n' or response == 'N':
translation = False
else:
translation = 'Maybe'
return translation
def showModes():
print("Modes:")
print('(0) Quit')
print('\n'.join(
['(' + str(i + 1) + ') ' + mode for i, mode in enumerate(modes_list)]))
def getChoice():
haveChoice = False
while not haveChoice:
raw = input("Select a mode from above (0 to 8): ")
if raw.isdigit():
num = int(raw)
if num in range(0, 8 + 1):
haveChoice = True
return num
def selectMode(choice):
game = ''
if choice == 0:
print("Quitting...")
game = 'quit'
else:
print("You chose ", end='')
print(modes_list[choice-1] + '.\n')
if choice == 1:
game = veryEasyGame()
if choice == 2:
game = easyGame()
if choice == 3:
game = normalGame()
if choice == 4:
game = hardGame()
if choice == 5:
game = veryHardGame()
if choice == 6:
game = nightmareGame()
if choice == 7:
game = impossibleGame()
if choice == 8:
game = randomGame()
return game
def play():
playing = True
round_number = 1
total_score = 0
showIntro()
while playing:
if not havePermission(round_number):
playing = False
break
print()
showModes()
print()
mode_num = getChoice()
game = selectMode(mode_num)
if game != 'quit':
game.play()
total_score += game.calculate_score()
else:
playing = False
break
round_number += 1
print("\nThanks for playing!")
if round_number > 1:
print('Your average score was {0:.1f} / 100.0'.format(total_score / (round_number-1)))
play()
from math import ceil
from random import sample
def prime_sieve(N):
"""Sieve of Eratosthenes with an upper bound.
Args:
N (int +): The upper bound.
Returns:
list: All prime numbers <= N.
"""
is_prime = [True for n in range(N + 1)]
for n in range(2, ceil(N**0.5)):
if is_prime[n]:
for multiple in range(n + n, N + 1, n):
is_prime[multiple] = False
primes = []
for n in range(2, N + 1):
if is_prime[n]:
primes.append(n)
return primes
def prime_sieve_range(a, b):
"""Sieve of Eratosthenes with a lower bound and upper bound.
Args:
a (int +): Lower bound.
b (int +a): Upper bound.
Returns:
list: All prime numbers between a and b, inclusive.
"""
return [prime for prime in prime_sieve(b) if prime >= a]
def random_copies(elements, c):
"""Copies of random elements of some list.
Args:
elements (list): A list of values.
c (int +): Number of copies.
Returns:
list: Copies of random elements, sorted.
Example:
random_copies([2, 3, 11], 4)
=> [2, 2, 3, 3], or
=> [2, 3, 11, 11], or
=> [3, 3, 3, 11], and so on.
"""
copies = []
for _ in range(c):
copy = sample(elements, 1)[0]
copies.append(copy)
return sorted(copies)
def random_primes(a, b, n, c=0):
"""Random prime numbers in a given range.
Args:
a (int +): Lower bound for the range of possible primes.
b (int +a): Upper bound for the range of possible primes.
n (int +): Number of random unique primes to generate.
c (int +0, optional): Number of random copies of the unique primes.
Returns:
list: n sorted random primes between a and b, inclusive, with c random copies.
"""
primes = prime_sieve_range(a, b)
chosen_primes = sample(primes, n)
copies = random_copies(chosen_primes, c)
return sorted(chosen_primes + copies)
def product(factors):
"""The product of a list of numbers.
Args:
factors (list): A list of numbers.
Returns:
int: The product of all of the factors.
"""
p = 1
for factor in factors:
p *= factor
return p
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment