Now that I’ve got a YahtzeeHand
class that is able to detect all of the common Yahtzee hands, I want to develop a scorecard that will allow me to keep track of each score in the game. The tricky part about Yahtzee is that once you “score” a hand, you cannot score another hand of that type for the rest of the game. For example, once you use the Full House, if you roll another Full House, you have to either re-roll the whole thing, or count a triple or double of some type.
Because of this, I think I’ll build a smart-ish Scorecard class that can keep track of each type of hand and also suggest the “open” items available to still be scored. I’ll start off with making a dictionary of “scores” and a simple method to evaluate a hand and apply the score to this dictionary. We’ll also use a cool feature of Python, specifically the ability to store an actual function as the value in a dictionary item to calculate the scores. Here is what it looks like:
from core.hand import YahtzeeHand class Scorecard: def __init__(self): self.scores = { "1": None, "2": None, "3": None, "4": None, "5": None, "6": None, "3Kind": None, "4Kind": None, "FullHouse": None, "SmallStraight": None, "LargeStraight": None, "Yahtzee": None, "Chance": None } self.cur_score = 0
I’ll also create a class variable as a dictionary of functions that can be used to evaluate the score of each type of hand. This is what it looks like:
class Scorecard: ... SCORE_METHODS = { "1": (lambda hand: hand.get_counts()[0] * 1), "2": (lambda hand: hand.get_counts()[1] * 2), "3": (lambda hand: hand.get_counts()[2] * 3), "4": (lambda hand: hand.get_counts()[3] * 4), "5": (lambda hand: hand.get_counts()[4] * 5), "6": (lambda hand: hand.get_counts()[5] * 6), "3Kind": (lambda hand: hand.get_hand_total_score() if hand.is_triple() else 0), "4Kind": (lambda hand: hand.get_hand_total_score() if hand.is_four_of_a_kind() else 0), "FullHouse": (lambda hand: 25 if hand.is_full_house() else 0), "SmallStraight": (lambda hand: 30 if hand.is_small_straight() else 0), "LargeStraight": (lambda hand: 40 if hand.is_large_straight() else 0), "Yahtzee": (lambda hand: 50 if hand.is_yahtzee() else 0), "Chance": (lambda hand: hand.get_hand_total_score()), }
Setting up the scoring like this allows me to take a YahtzeeHand
as input and in just a couple of lines of code, output the value of that hand for every item on the scorecard:
def all_possible_scores(self, hand: YahtzeeHand) -> dict: retval = {} for (hand_type, score_method) in Scorecard.SCORE_METHODS.items(): retval[hand_type] = score_method(hand) return retval
Using the following code, we get this output which looks like everything is working well:
if __name__ == "__main__": hands = [YahtzeeHand() for i in range(10)] sc = Scorecard() for h in hands: print(f"Hand: {h.get()}\nScores: {sc.all_possible_scores(h)}")
Hand: (5, 6, 6, 2, 3) Scores: {'1': 0, '2': 2, '3': 3, '4': 0, '5': 5, '6': 12, '3Kind': 0, '4Kind': 0, 'FullHouse': 0, 'SmallStraight': 0, 'LargeStraight': 0, 'Yahtzee': 0, 'Chance': 22} Hand: (4, 5, 3, 4, 5) Scores: {'1': 0, '2': 0, '3': 3, '4': 8, '5': 10, '6': 0, '3Kind': 0, '4Kind': 0, 'FullHouse': 0, 'SmallStraight': 0, 'LargeStraight': 0, 'Yahtzee': 0, 'Chance': 21} Hand: (4, 6, 1, 5, 3) Scores: {'1': 1, '2': 0, '3': 3, '4': 4, '5': 5, '6': 6, '3Kind': 0, '4Kind': 0, 'FullHouse': 0, 'SmallStraight': 30, 'LargeStraight': 0, 'Yahtzee': 0, 'Chance': 19} Hand: (2, 2, 3, 2, 6) Scores: {'1': 0, '2': 6, '3': 3, '4': 0, '5': 0, '6': 6, '3Kind': 15, '4Kind': 0, 'FullHouse': 0, 'SmallStraight': 0, 'LargeStraight': 0, 'Yahtzee': 0, 'Chance': 15} Hand: (1, 6, 5, 4, 2) ...
It’s clear that things are working relatively well — the class appears to be detecting each type of hand, and also scoring the chance correctly. This is good enough for me to move onto the next step — applying the score to the scorecard. Once we finish that, we can start to build an actual Yahtzee game!!