Advertisement
GMan_LDN

Untitled

Feb 8th, 2025
132
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 11.83 KB | None | 0 0
  1. import tkinter as tk
  2. from tkinter import messagebox
  3. import random
  4.  
  5. # List of scoring categories in standard Yahtzee
  6. CATEGORIES = [
  7. "Ones", "Twos", "Threes", "Fours", "Fives", "Sixes",
  8. "Three of a Kind", "Four of a Kind", "Full House",
  9. "Small Straight", "Large Straight", "Yahtzee", "Chance"
  10. ]
  11.  
  12. class YahtzeeGameGUI:
  13. def __init__(self, master):
  14. self.master = master
  15. master.title("Yahtzee")
  16.  
  17. # Initialize game state for two players.
  18. # Each player's score sheet is a dictionary: category -> score (or None if not yet chosen)
  19. self.players = [
  20. {"name": "Player 1", "scores": {cat: None for cat in CATEGORIES}},
  21. {"name": "Player 2", "scores": {cat: None for cat in CATEGORIES}}
  22. ]
  23. self.current_player_index = 0
  24. # Count the number of turns each player has taken (should reach 13 turns per player)
  25. self.turns_taken = [0, 0]
  26.  
  27. # Turn-specific state variables
  28. self.current_roll = 0 # 0 means not rolled yet; max allowed is 3
  29. self.dice = [None] * 5 # Holds the value for each of the 5 dice (None until rolled)
  30. self.held = [False] * 5 # Holds whether a die is "held"
  31.  
  32. # ------------------------------
  33. # Build the GUI components
  34. # ------------------------------
  35.  
  36. # Info frame: shows current player and roll number
  37. self.info_frame = tk.Frame(master)
  38. self.info_frame.pack(pady=5)
  39. self.player_label = tk.Label(self.info_frame, text="Current Player: Player 1 - Roll: 0", font=("Arial", 14))
  40. self.player_label.pack()
  41.  
  42. # Dice frame: each die is represented as a button that can be clicked to toggle hold
  43. self.dice_frame = tk.Frame(master)
  44. self.dice_frame.pack(pady=5)
  45. self.dice_buttons = []
  46. for i in range(5):
  47. btn = tk.Button(
  48. self.dice_frame,
  49. text="?",
  50. width=4,
  51. height=2,
  52. font=("Arial", 18),
  53. command=lambda i=i: self.toggle_hold(i)
  54. )
  55. btn.grid(row=0, column=i, padx=5, pady=5)
  56. self.dice_buttons.append(btn)
  57.  
  58. # Roll dice button
  59. self.roll_button = tk.Button(master, text="Roll Dice", font=("Arial", 14), command=self.roll_dice)
  60. self.roll_button.pack(pady=5)
  61.  
  62. # Score selection frame: shows one button per category.
  63. # The buttons are initially disabled until at least one roll has been made.
  64. self.score_frame = tk.Frame(master)
  65. self.score_frame.pack(pady=5)
  66. self.score_buttons = {}
  67. for cat in CATEGORIES:
  68. btn = tk.Button(
  69. self.score_frame,
  70. text=cat,
  71. font=("Arial", 12),
  72. command=lambda cat=cat: self.choose_score(cat),
  73. state=tk.DISABLED,
  74. width=25,
  75. anchor="w"
  76. )
  77. btn.pack(pady=2)
  78. self.score_buttons[cat] = btn
  79.  
  80. # Scoreboard frame: shows the scores of both players
  81. self.scoreboard_frame = tk.Frame(master)
  82. self.scoreboard_frame.pack(pady=10)
  83. self.scoreboard_label = tk.Label(self.scoreboard_frame, text=self.get_scoreboard_text(), font=("Courier", 10), justify="left")
  84. self.scoreboard_label.pack()
  85.  
  86. def roll_dice(self):
  87. """Rolls all dice that are not held (up to 3 rolls per turn)."""
  88. if self.current_roll < 3:
  89. # For every die not held, assign a random value between 1 and 6.
  90. for i in range(5):
  91. if not self.held[i]:
  92. self.dice[i] = random.randint(1, 6)
  93. self.current_roll += 1
  94. self.update_dice_display()
  95. self.update_info_label()
  96. self.update_score_buttons_state()
  97. else:
  98. messagebox.showinfo("Info", "You have already rolled 3 times this turn.")
  99.  
  100. def update_dice_display(self):
  101. """Updates the dice buttons to show current values and held status."""
  102. for i, btn in enumerate(self.dice_buttons):
  103. value = self.dice[i]
  104. if value is None:
  105. btn.config(text="?", relief=tk.RAISED)
  106. else:
  107. # Change the button relief to indicate if the die is held.
  108. btn.config(text=str(value), relief=tk.SUNKEN if self.held[i] else tk.RAISED)
  109.  
  110. def update_info_label(self):
  111. """Updates the info label with current player name and roll count."""
  112. player_name = self.players[self.current_player_index]["name"]
  113. self.player_label.config(text=f"Current Player: {player_name} - Roll: {self.current_roll}")
  114.  
  115. def toggle_hold(self, index):
  116. """Toggles the hold status of a die if at least one roll has been made."""
  117. if self.current_roll > 0:
  118. self.held[index] = not self.held[index]
  119. self.update_dice_display()
  120.  
  121. def update_score_buttons_state(self):
  122. """
  123. Enables the scoring buttons (only for categories not yet used) once the player
  124. has rolled at least once and updates the button text to show the potential score.
  125. """
  126. state = tk.NORMAL if self.current_roll > 0 else tk.DISABLED
  127. for cat, btn in self.score_buttons.items():
  128. # Only allow scoring if the category has not been used yet.
  129. if self.players[self.current_player_index]["scores"][cat] is None:
  130. btn.config(state=state)
  131. potential = self.compute_score(self.dice, cat)
  132. btn.config(text=f"{cat} (potential: {potential})")
  133. else:
  134. btn.config(state=tk.DISABLED)
  135. btn.config(text=f"{cat} (scored: {self.players[self.current_player_index]['scores'][cat]})")
  136.  
  137. def compute_score(self, dice, category):
  138. """
  139. Given a list of five dice (ints), compute the score for the given category.
  140. Returns 0 if the dice do not satisfy the category requirements.
  141. """
  142. # If any dice are missing (i.e. still None), return 0.
  143. if any(d is None for d in dice):
  144. return 0
  145.  
  146. counts = {i: dice.count(i) for i in range(1, 7)}
  147. total = sum(dice)
  148.  
  149. if category == "Ones":
  150. return counts.get(1, 0) * 1
  151. elif category == "Twos":
  152. return counts.get(2, 0) * 2
  153. elif category == "Threes":
  154. return counts.get(3, 0) * 3
  155. elif category == "Fours":
  156. return counts.get(4, 0) * 4
  157. elif category == "Fives":
  158. return counts.get(5, 0) * 5
  159. elif category == "Sixes":
  160. return counts.get(6, 0) * 6
  161. elif category == "Three of a Kind":
  162. if any(count >= 3 for count in counts.values()):
  163. return total
  164. else:
  165. return 0
  166. elif category == "Four of a Kind":
  167. if any(count >= 4 for count in counts.values()):
  168. return total
  169. else:
  170. return 0
  171. elif category == "Full House":
  172. # A full house is exactly a 3-of-a-kind and a pair (e.g. 2,2,3,3,3)
  173. counts_list = sorted([count for count in counts.values() if count > 0])
  174. if counts_list == [2, 3]:
  175. return 25
  176. else:
  177. return 0
  178. elif category == "Small Straight":
  179. # Check for any 4 consecutive numbers.
  180. unique = set(dice)
  181. straights = [{1, 2, 3, 4}, {2, 3, 4, 5}, {3, 4, 5, 6}]
  182. for s in straights:
  183. if s.issubset(unique):
  184. return 30
  185. return 0
  186. elif category == "Large Straight":
  187. unique = set(dice)
  188. if unique == {1, 2, 3, 4, 5} or unique == {2, 3, 4, 5, 6}:
  189. return 40
  190. return 0
  191. elif category == "Yahtzee":
  192. if any(count == 5 for count in counts.values()):
  193. return 50
  194. else:
  195. return 0
  196. elif category == "Chance":
  197. return total
  198. return 0
  199.  
  200. def choose_score(self, category):
  201. """
  202. Called when a player selects a scoring category.
  203. The function computes the score for the current dice,
  204. records it for the current player, updates the scoreboard,
  205. and then moves to the next player's turn.
  206. """
  207. score = self.compute_score(self.dice, category)
  208. current_player = self.players[self.current_player_index]
  209. current_player["scores"][category] = score
  210. messagebox.showinfo("Score Recorded",
  211. f"{current_player['name']} scored {score} in {category}")
  212. self.turns_taken[self.current_player_index] += 1
  213. self.update_score_buttons_state()
  214. self.update_scoreboard()
  215.  
  216. # Check if the game is over (all 13 turns taken by both players)
  217. if self.check_game_over():
  218. self.end_game()
  219. else:
  220. self.next_turn()
  221.  
  222. def update_scoreboard(self):
  223. """Refreshes the scoreboard display."""
  224. self.scoreboard_label.config(text=self.get_scoreboard_text())
  225.  
  226. def get_scoreboard_text(self):
  227. """Builds a text string showing each category and both players’ scores."""
  228. text = "Scoreboard:\n"
  229. text += f"{'Category':20s} {'Player 1':>8s} {'Player 2':>8s}\n"
  230. text += "-" * 40 + "\n"
  231. for cat in CATEGORIES:
  232. score1 = self.players[0]["scores"][cat]
  233. score2 = self.players[1]["scores"][cat]
  234. score1_text = str(score1) if score1 is not None else "-"
  235. score2_text = str(score2) if score2 is not None else "-"
  236. text += f"{cat:20s} {score1_text:>8s} {score2_text:>8s}\n"
  237. total1 = sum(score for score in self.players[0]["scores"].values() if score is not None)
  238. total2 = sum(score for score in self.players[1]["scores"].values() if score is not None)
  239. text += "-" * 40 + "\n"
  240. text += f"{'Total':20s} {total1:>8d} {total2:>8d}"
  241. return text
  242.  
  243. def next_turn(self):
  244. """Resets the turn state and switches to the other player."""
  245. # Switch to the other player
  246. self.current_player_index = 1 - self.current_player_index
  247. # Reset the roll count, dice values, and held status
  248. self.current_roll = 0
  249. self.dice = [None] * 5
  250. self.held = [False] * 5
  251.  
  252. # Reset dice button display
  253. for btn in self.dice_buttons:
  254. btn.config(text="?", relief=tk.RAISED)
  255.  
  256. self.update_info_label()
  257. self.update_score_buttons_state()
  258.  
  259. def check_game_over(self):
  260. """
  261. The game is over if both players have completed 13 turns
  262. (i.e. filled all 13 categories).
  263. """
  264. return all(turns >= 13 for turns in self.turns_taken)
  265.  
  266. def end_game(self):
  267. """Ends the game and shows the final scores and winner."""
  268. total1 = sum(score for score in self.players[0]["scores"].values() if score is not None)
  269. total2 = sum(score for score in self.players[1]["scores"].values() if score is not None)
  270. if total1 > total2:
  271. winner = "Player 1 wins!"
  272. elif total2 > total1:
  273. winner = "Player 2 wins!"
  274. else:
  275. winner = "It's a tie!"
  276. messagebox.showinfo("Game Over",
  277. f"Game Over!\nPlayer 1: {total1}\nPlayer 2: {total2}\n{winner}")
  278. # Disable further interaction by disabling roll, dice, and score buttons
  279. self.roll_button.config(state=tk.DISABLED)
  280. for btn in self.score_buttons.values():
  281. btn.config(state=tk.DISABLED)
  282. for btn in self.dice_buttons:
  283. btn.config(state=tk.DISABLED)
  284.  
  285. if __name__ == "__main__":
  286. root = tk.Tk()
  287. game = YahtzeeGameGUI(root)
  288. root.mainloop()
  289.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement