generated from mwc/lab_dice
When I write code, Im focused on how to make something work and making sure everything runs correctly. But when I write docstrings Im thinking about how to explain the purpose of the method and how it works in a way another person could understand.
230 lines
8.7 KiB
Python
230 lines
8.7 KiB
Python
from die import Die
|
||
|
||
class Yahtzee:
|
||
"""A command-line Yahtzee game.
|
||
This version of Yahtzee is initialized with a list of goals.
|
||
"""
|
||
def __init__(self, goals):
|
||
self.score = 0
|
||
self.goals = goals
|
||
self.dice = [Die() for num in range(5)]
|
||
|
||
def play(self):
|
||
"""Play an entire game.
|
||
Starts by greeting the user, then plays rounds until all the goals
|
||
have been used. When the game is over, tells the player their final
|
||
score.
|
||
"""
|
||
print("Welcome to Yachtzee!")
|
||
self.score = 0
|
||
for goal in self.goals:
|
||
goal.used = False
|
||
while self.count_unused_goals() > 0:
|
||
self.play_round()
|
||
print(f"Your final score was {self.score}")
|
||
|
||
def play_round(self):
|
||
"""
|
||
Plays a single round of the game.
|
||
|
||
This method resets the number of rolls left, rolls all dice once,
|
||
and displays the current game status to the player. It then prompts
|
||
the player to choose a scoring goal for the round, marks that goal
|
||
as used, and updates the player's total score based on the dice
|
||
rolled. The method manages the full sequence of actions required
|
||
to complete one round from start to finish.
|
||
"""
|
||
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):
|
||
"""
|
||
Displays the current game status to the player.
|
||
|
||
This method gathers the values of all dice, formats them into a
|
||
readable string, and prints the player's current score, the number
|
||
of rolls remaining, and the current state of the dice. It is used
|
||
to give the player a clear snapshot of their progress during a round.
|
||
"""
|
||
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):
|
||
"""
|
||
Prompts the player to choose a scoring goal or re-roll.
|
||
|
||
This method gathers all unused scoring goals and displays them as
|
||
options for the player, along with a “Re-roll” option if rolls are
|
||
still available. It uses the player's choice to either trigger a
|
||
re-roll—after which it shows the updated status and recursively
|
||
prompts again—or to return the selected scoring goal. This method
|
||
manages the full decision-making process for selecting how the
|
||
current dice should be scored.
|
||
"""
|
||
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):
|
||
"""
|
||
Gets a valid choice from the player based on a list of options.
|
||
|
||
This method prints a numbered list of available options and prompts
|
||
the player for an input. It repeatedly checks the player's response
|
||
using `option_choice_is_valid()` and continues asking until a valid
|
||
selection is entered. Once validated, the method returns the chosen
|
||
option as an integer index that can be used to access the selected
|
||
item in the options list.
|
||
"""
|
||
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):
|
||
"""
|
||
Checks whether the player's menu choice is valid.
|
||
|
||
This method verifies that the player's input is a digit and then
|
||
converts it to an integer to ensure it falls within the valid range
|
||
of option indices. It returns False if the input is not numeric,
|
||
negative, or exceeds the number of available options. Otherwise,
|
||
it returns True, indicating that the choice can be safely used.
|
||
"""
|
||
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):
|
||
"""
|
||
Returns the number of scoring goals that have not been used yet.
|
||
|
||
This method calls `get_unused_goals()` to retrieve the list of all
|
||
goals that are still available for scoring and returns the length
|
||
of that list. It serves as a quick way to track how many scoring
|
||
options remain in the game.
|
||
"""
|
||
return len(self.get_unused_goals())
|
||
|
||
|
||
def get_unused_goals(self):
|
||
"""
|
||
Returns a list of all scoring goals that have not been used yet.
|
||
|
||
This method iterates through the full collection of goals and checks
|
||
each one’s `used` attribute. Any goal that has not been marked as used
|
||
is added to a new list, which is returned at the end. This provides the
|
||
game with an up-to-date list of scoring options still available to the
|
||
player.
|
||
"""
|
||
unused_goals = []
|
||
for goal in self.goals:
|
||
if not goal.used:
|
||
unused_goals.append(goal)
|
||
return unused_goals
|
||
|
||
|
||
def reroll(self):
|
||
"""
|
||
Performs a reroll of selected dice during the round.
|
||
|
||
This method decreases the number of rolls left, retrieves the list of
|
||
dice indices the player is allowed to reroll, and then determines which
|
||
dice the player actually wants to reroll by calling `get_dice_to_reroll()`.
|
||
It then loops through the chosen dice and rolls each one. This function
|
||
manages the full reroll process, updating both the game state and the dice.
|
||
"""
|
||
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):
|
||
"""
|
||
Determines which dice the player wants to reroll based on their choices.
|
||
|
||
This method receives a list of integers representing the dice faces the
|
||
player selected for rerolling. It iterates through all dice in the game,
|
||
checks whether each die's current face value appears in the list, and if
|
||
so, removes that value from the list and adds the die to the reroll list.
|
||
This ensures that each selected face value corresponds to one die only.
|
||
The method returns a list of dice objects that should be rerolled.
|
||
"""
|
||
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):
|
||
"""
|
||
Prompts the player to choose which dice they want to reroll.
|
||
|
||
This method asks the player to enter the face values of the dice they
|
||
wish to reroll as a string of digits. It repeatedly validates the input
|
||
using `reroll_choices_are_valid()` and continues prompting until the
|
||
entered choices meet the required format. Once validated, the method
|
||
converts each character in the input string into an integer and returns
|
||
the resulting list of selected face values.
|
||
"""
|
||
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):
|
||
"""
|
||
Validates the player's input for selecting dice to reroll.
|
||
|
||
This method first checks that the player's input consists only of
|
||
digits. It then converts each digit into an integer representing a
|
||
desired die face. The method verifies these selections by matching
|
||
each requested face value to an existing die face in the current set
|
||
of dice. For every valid match, that face value is removed from the
|
||
list of choices. If, after processing all dice, no unmatched values
|
||
remain, the input is considered valid. Otherwise, the method returns
|
||
False to indicate an invalid selection.
|
||
"""
|
||
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
|