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