aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorThomas Voss <mail@thomasvoss.com> 2026-02-26 13:37:25 +0100
committerThomas Voss <mail@thomasvoss.com> 2026-02-26 13:37:25 +0100
commitebbaac3816c2bf60fce8c77cfb5b750ae912fc05 (patch)
tree9407e261efc79c1b7db7f276a551f796eddd8b75
parent1254842c5dc23e2534c201deead4b4012a4faa54 (diff)
Add Yatzy in Python
-rw-r--r--python/yatzy/yatzy.py255
1 files changed, 255 insertions, 0 deletions
diff --git a/python/yatzy/yatzy.py b/python/yatzy/yatzy.py
new file mode 100644
index 0000000..61dd376
--- /dev/null
+++ b/python/yatzy/yatzy.py
@@ -0,0 +1,255 @@
+import random
+import sys
+import os
+
+
+# Clear the screen
+def clear_screen():
+ if os.name == 'nt': # Windows
+ os.system('cls')
+ else: # Everyone else
+ os.system('clear')
+
+
+# Count the number of unique pairs in DICE
+def number_of_pairs(dice):
+ pairs = 0
+ for i in range(1, 7):
+ pairs += dice.count(i) >= 2
+ return pairs
+
+
+# Check if all the dice are valid
+def dice_are_valid(dice):
+ for x in dice:
+ if x < 1 or x > 5:
+ return False
+ return True
+
+
+# Calculate the score for CATEGORY with the given DICE
+def calculate_score(category, dice):
+ match category:
+ case 'Ones':
+ return dice.count(1)
+ case 'Twos':
+ return dice.count(2) * 2
+ case 'Threes':
+ return dice.count(3) * 3
+ case 'Fours':
+ return dice.count(4) * 4
+ case 'Fives':
+ return dice.count(5) * 5
+ case 'Sixes':
+ return dice.count(6) * 6
+ case 'One Pair':
+ for i in range(6, 0, -1):
+ if dice.count(i) >= 2:
+ return i * 2
+ return 0
+ case 'Two Pairs':
+ if number_of_pairs(dice) != 2:
+ return 0
+
+ # Sum all of the pairs. There are only 5 dice, so it is not
+ # possible that we have more than 2 pairs.
+ n = 0
+ for i in range(6, 0, -1):
+ if dice.count(i) >= 2:
+ n += i * 2
+ return n
+ case 'Three of a Kind':
+ for i in range(6, 0, -1):
+ if dice.count(i) >= 3:
+ return i * 3
+ return 0
+ case 'Four of a Kind':
+ for i in range(6, 0, -1):
+ if dice.count(i) >= 4:
+ return i * 4
+ return 0
+ case 'Small Straight':
+ if set(dice) == {1, 2, 3, 4, 5}:
+ return 15
+ return 0
+ case 'Large Straight':
+ if set(dice) == {2, 3, 4, 5, 6}:
+ return 20
+ return 0
+ case 'Full House':
+ # If there are 2 pairs, but only 2 different dice, it means (because
+ # there are only 5 dice total) that we have a full house
+ if len(set(dice)) == 2 and number_of_pairs(dice) == 2:
+ return sum(dice)
+ return 0
+ case 'Chance':
+ return sum(dice)
+ case 'Yatzy':
+ if len(set(dice)) == 1:
+ return 50
+ return 0
+
+
+# Calculate the total score factoring in the bonus
+def calculate_total_score(scores):
+ n = sum(x for x in scores.values() if x != -1)
+ upper_section = (
+ scores['Ones'] +
+ scores['Twos'] +
+ scores['Threes'] +
+ scores['Fours'] +
+ scores['Fives'] +
+ scores['Sixes']
+ )
+ if upper_section >= 63:
+ n += 50
+ return n
+
+
+# Check if the game is over (because the player has filled in the entire score
+# sheet)
+def game_is_over(scores):
+ return -1 not in scores.values()
+
+
+# Main game loop
+def main():
+ print('Enter the players name or ‹ENTER› if all names have been entered.')
+
+ # Read the players’ names
+ names = []
+ scores = {}
+
+ while True:
+ name = input('Name => ').strip()
+ if name == '':
+ break
+ names.append(name)
+ scores[name] = {
+ 'Ones': -1,
+ 'Twos': -1,
+ 'Threes': -1,
+ 'Fours': -1,
+ 'Fives': -1,
+ 'Sixes': -1,
+ 'One Pair': -1,
+ 'Two Pairs': -1,
+ 'Three of a Kind': -1,
+ 'Four of a Kind': -1,
+ 'Small Straight': -1,
+ 'Large Straight': -1,
+ 'Full House': -1,
+ 'Chance': -1,
+ 'Yatzy': -1,
+ }
+
+ if len(names) == 0:
+ print('No players found! Exiting…')
+ sys.exit(0)
+
+ # Check if the game is over using the score sheet of the first player
+ while not game_is_over(scores[names[0]]):
+ for player in names:
+ # Initialize variables
+ score = calculate_total_score(scores[player])
+ rolls_left = 2
+ dice = [random.randint(1, 6) for _ in range(5)]
+
+ # Clear the screen and print a header, then ask which dice to reroll
+ # and reroll if appropriate
+ while rolls_left > 0:
+ clear_screen()
+
+ print(f'Current score for {player}: {score}')
+ print(f'Your current dice: {dice}\n')
+ print(f'Which dice (1–5) would you like to reroll? (Rolls left: {rolls_left})')
+ print('Enter the indicies space-separated or press ‹ENTER› to keep all\n')
+
+ while True:
+ xs = []
+ s = input('Dice => ').strip()
+ if s == '':
+ break
+
+ xs = s.split(' ')
+ try:
+ xs = [int(x) for x in xs]
+ except ValueError:
+ print('Invalid dice given. Please try again!')
+ else: # If there was no exception
+ if dice_are_valid(xs):
+ break
+ print('Invalid dice given. Please try again!')
+
+ if xs == []:
+ break
+
+ # We do x - 1 because the list starts at 0, but the user will be
+ # counting from 1.
+ for x in xs:
+ dice[x - 1] = random.randint(1, 6)
+
+ rolls_left -= 1
+
+ # Show the possible categories that the player can select, and
+ # prompt them for a category to pick
+ clear_screen()
+ player_scores = scores[player]
+
+ print(f'Current score for {player}: {score}')
+ print(f'Your current dice: {dice}\n')
+
+ print('Your score sheet:')
+ for category in player_scores:
+ n = player_scores[category]
+ if n == -1:
+ n = '-'
+ print(f'{category}: {n}')
+ print()
+
+ print('Select a category:')
+
+ i = 1
+ index_to_category_name = {}
+
+ for category in player_scores:
+ if player_scores[category] == -1:
+ print(f'{i}. {category}')
+ index_to_category_name[i] = category
+ i += 1
+ print()
+
+ while True:
+ try:
+ selected_category = int(input('Category => '))
+ except ValueError:
+ print('Invalid category number. Please try again!')
+ else:
+ if selected_category in index_to_category_name:
+ break
+ print('Invalid category number. Please try again!')
+ category_name = index_to_category_name[selected_category]
+
+ player_scores[category_name] = calculate_score(category_name, dice)
+
+ clear_screen()
+ print('Game Over!\n')
+ print('Leaderboard:')
+
+ total_scores = []
+ for player in names:
+ pair = [
+ calculate_total_score(scores[player]),
+ player,
+ ]
+ total_scores.append(pair)
+
+ total_scores.sort(reverse=True)
+
+ for pair in total_scores:
+ print(f'{pair[1]}: {pair[0]}')
+
+ print()
+ input('Press any key to exit...')
+
+main()