lab_dice/yahtzee.py

180 lines
7.8 KiB
Python

from die import Die
class Yachtzee:
"""A command-line Yahtzee game.
This version of Yahtzee is initialized with a list of goals.
"""
def __init__(self, goals):
"""Defines elements of game in order to play.
This menthod established the three elements needed to play the game and create the rest of the rules
and code. 1. It establishes the score, 2. Goals and 3. dice. The die already exists and is defined as
a method and imported using the code above.
"""
self.score = 0
self.goals = goals
self.dice = [Die() for num in range(5)]
def play(self):
"""Play an entire game. Starts by greeting th user, then plays rounds
until all the goals have been used. When the games is over, tells the player
the final score.
"""
print("Welcome to Yachtzee!")
while self.count_unused_goals() > 0:
self.play_round()
print(f"Your final score was {self.score}")
def play_round(self):
""" Plays through one round/roll.
Starts by rolling dice and showing status/results of the roll.
prompts player to choose a goal, essntially selecting the roll/result that they wish to keep.
Once a goal is chosen, then the goal used is saved and scored to be ready for the next round.
"""
print("=" * 80)
self.rolls_left = 3
for die in self.dice:
die.roll()
self.show_status()
goal = self.choose_goal()
goal.used = True
self.score += goal.score(self.dice)
def show_status(self):
"""Defines what is to be displayed each round.
When each round is complete, there is data that needs to be communicated to the player needs
to know to continue the game. This tells the game to display that information; socre, rolls
left, and the dice.
"""
dice = ', '.join([str(die) for die in self.dice])
print(f"Score: {self.score}. Rolls left: {self.rolls_left}. Dice: {dice}.")
def choose_goal(self):
"""Establishes that one goal is used per round and allows for choise.
This method establishes goals for the games and the way that they are dispensed and
recycled to the player. It cycles through the goals and chooses one then creates options for the
unsued ones. Then it prompts to display the rolls left in the games.
It then establishes the options after the player chooses their option. the options are the chance to
re-roll (as long as the number of rounds allows).
Finally it will return this information to the player so they can continue the game.
"""
options = []
unused_goals = self.get_unused_goals()
for goal in unused_goals:
option = goal.prompt(self.dice)
options.append(option)
if self.rolls_left > 0:
options.append("Re-roll")
choice = self.get_choice(options)
if options[choice] == "Re-roll":
self.reroll()
self.show_status()
return self.choose_goal()
else:
return unused_goals[choice]
def get_choice(self, options):
"""Creates print to explain options.
This section of code assigns actual verbal (written) explaintions to the data that the previous
method defines. This makes it easier for the player to understand the data given back to them
and really makes the steps in the game possible to decipher.
"""
print("What would you like to do?")
for i, option in enumerate(options):
print(f"{i}. {option}")
choice = input("> ")
while not self.option_choice_is_valid(choice, options):
print("Sorry, that's not a valid choice.")
choice = input("> ")
return int(choice)
def option_choice_is_valid(self, choice, options):
"""Allows for choice that is correct by game rules.
This method defines what an allowable option is and how to allow that option. If the player chooses a
valid option then this will allow them to do so.
"""
if not choice.isdigit():
return False
if int(choice) < 0:
return False
if int(choice) >= len(options):
return False
return True
def count_unused_goals(self):
"""Defines unused goals.
This distinguishes between goals that have been used by the player and un-used goals,
that are still remaining options for the player.
"""
return len(self.get_unused_goals())
def get_unused_goals(self):
"""Further distinguish un-used goals from usable options.
if the goal is not the goal used, the it is categorized as an unused goal. This allows for
it to continue to exist as an unused goal.
"""
unused_goals = []
for goal in self.goals:
if not goal.used:
unused_goals.append(goal)
return unused_goals
def reroll(self):
"""Defines the reroll steo in a broad sense.
First is calculates the re-rolls that are still allowed in the game, since there are a
limited number of rolls. Then it prompts the game to define the choices for re-roll options.
this is followed by an if/then senario. This allows for the action of a re-roll.
"""
self.rolls_left -= 1
choices = self.get_reroll_choices()
dice_to_reroll = self.get_dice_to_reroll(choices)
for die in dice_to_reroll:
die.roll()
def get_dice_to_reroll(self, choice_ints):
"""Prompts dice to re-roll.
The game rolls then loops through all the dice options in the list to identify the roll.
Next, it defines if the numbers rolled match any of the choices for hands that would be
playable. Then it eill add the dice to the list that can be rolled again. and re-rolled.
"""
dice_to_reroll = []
for die in self.dice:
if die.face in choice_ints:
choice_ints.remove(die.face)
dice_to_reroll.append(die)
return dice_to_reroll
def get_reroll_choices(self):
"""Prints options for player to choose and continue the game.
This give the player the written prompt to choose the dice that they want to re-roll in the game.
Then the player chooses and the game gives them feedback. Either the choice they make is valid and
the game will continue or, it will not continue with invalid choice.
It then converts the player's choice into integers with "int" like in the numbers assignment.
Finally, it gives all of this information back to the player to review and re-roll or continue the game.
"""
print("Which dice do you want to re-roll?")
choices = input("> ")
while not self.reroll_choices_are_valid(choices):
print("Please enter the numbers on dice you want to re-roll.")
choices = input("> ")
choice_ints = [int(digit) for digit in choices]
return choice_ints
def reroll_choices_are_valid(self, choices_str):
"""Confirms that the player chose a valid re-roll.
This starts by using str to check if the string is digits. Then it changes that into a list of
integers (like above). The game then runs through these integers to figure out if there is a
combination that is a valid re-roll choice. Next it removes the choices from the roll. Finally,
it confirms that the choices were all valid integers.
"""
if not choices_str.isdigit():
return False
choice_ints = [int(digit) for digit in choices_str]
for die in self.dice:
if die.face in choice_ints:
choice_ints.remove(die.face)
return len(choice_ints) == 0