Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- import tkinter as tk
- from tkinter import messagebox
- import random
- # List of scoring categories in standard Yahtzee
- CATEGORIES = [
- "Ones", "Twos", "Threes", "Fours", "Fives", "Sixes",
- "Three of a Kind", "Four of a Kind", "Full House",
- "Small Straight", "Large Straight", "Yahtzee", "Chance"
- ]
- class YahtzeeGameGUI:
- def __init__(self, master):
- self.master = master
- master.title("Yahtzee")
- # Initialize game state for two players.
- # Each player's score sheet is a dictionary: category -> score (or None if not yet chosen)
- self.players = [
- {"name": "Player 1", "scores": {cat: None for cat in CATEGORIES}},
- {"name": "Player 2", "scores": {cat: None for cat in CATEGORIES}}
- ]
- self.current_player_index = 0
- # Count the number of turns each player has taken (should reach 13 turns per player)
- self.turns_taken = [0, 0]
- # Turn-specific state variables
- self.current_roll = 0 # 0 means not rolled yet; max allowed is 3
- self.dice = [None] * 5 # Holds the value for each of the 5 dice (None until rolled)
- self.held = [False] * 5 # Holds whether a die is "held"
- # ------------------------------
- # Build the GUI components
- # ------------------------------
- # Info frame: shows current player and roll number
- self.info_frame = tk.Frame(master)
- self.info_frame.pack(pady=5)
- self.player_label = tk.Label(self.info_frame, text="Current Player: Player 1 - Roll: 0", font=("Arial", 14))
- self.player_label.pack()
- # Dice frame: each die is represented as a button that can be clicked to toggle hold
- self.dice_frame = tk.Frame(master)
- self.dice_frame.pack(pady=5)
- self.dice_buttons = []
- for i in range(5):
- btn = tk.Button(
- self.dice_frame,
- text="?",
- width=4,
- height=2,
- font=("Arial", 18),
- command=lambda i=i: self.toggle_hold(i)
- )
- btn.grid(row=0, column=i, padx=5, pady=5)
- self.dice_buttons.append(btn)
- # Roll dice button
- self.roll_button = tk.Button(master, text="Roll Dice", font=("Arial", 14), command=self.roll_dice)
- self.roll_button.pack(pady=5)
- # Score selection frame: shows one button per category.
- # The buttons are initially disabled until at least one roll has been made.
- self.score_frame = tk.Frame(master)
- self.score_frame.pack(pady=5)
- self.score_buttons = {}
- for cat in CATEGORIES:
- btn = tk.Button(
- self.score_frame,
- text=cat,
- font=("Arial", 12),
- command=lambda cat=cat: self.choose_score(cat),
- state=tk.DISABLED,
- width=25,
- anchor="w"
- )
- btn.pack(pady=2)
- self.score_buttons[cat] = btn
- # Scoreboard frame: shows the scores of both players
- self.scoreboard_frame = tk.Frame(master)
- self.scoreboard_frame.pack(pady=10)
- self.scoreboard_label = tk.Label(self.scoreboard_frame, text=self.get_scoreboard_text(), font=("Courier", 10), justify="left")
- self.scoreboard_label.pack()
- def roll_dice(self):
- """Rolls all dice that are not held (up to 3 rolls per turn)."""
- if self.current_roll < 3:
- # For every die not held, assign a random value between 1 and 6.
- for i in range(5):
- if not self.held[i]:
- self.dice[i] = random.randint(1, 6)
- self.current_roll += 1
- self.update_dice_display()
- self.update_info_label()
- self.update_score_buttons_state()
- else:
- messagebox.showinfo("Info", "You have already rolled 3 times this turn.")
- def update_dice_display(self):
- """Updates the dice buttons to show current values and held status."""
- for i, btn in enumerate(self.dice_buttons):
- value = self.dice[i]
- if value is None:
- btn.config(text="?", relief=tk.RAISED)
- else:
- # Change the button relief to indicate if the die is held.
- btn.config(text=str(value), relief=tk.SUNKEN if self.held[i] else tk.RAISED)
- def update_info_label(self):
- """Updates the info label with current player name and roll count."""
- player_name = self.players[self.current_player_index]["name"]
- self.player_label.config(text=f"Current Player: {player_name} - Roll: {self.current_roll}")
- def toggle_hold(self, index):
- """Toggles the hold status of a die if at least one roll has been made."""
- if self.current_roll > 0:
- self.held[index] = not self.held[index]
- self.update_dice_display()
- def update_score_buttons_state(self):
- """
- Enables the scoring buttons (only for categories not yet used) once the player
- has rolled at least once and updates the button text to show the potential score.
- """
- state = tk.NORMAL if self.current_roll > 0 else tk.DISABLED
- for cat, btn in self.score_buttons.items():
- # Only allow scoring if the category has not been used yet.
- if self.players[self.current_player_index]["scores"][cat] is None:
- btn.config(state=state)
- potential = self.compute_score(self.dice, cat)
- btn.config(text=f"{cat} (potential: {potential})")
- else:
- btn.config(state=tk.DISABLED)
- btn.config(text=f"{cat} (scored: {self.players[self.current_player_index]['scores'][cat]})")
- def compute_score(self, dice, category):
- """
- Given a list of five dice (ints), compute the score for the given category.
- Returns 0 if the dice do not satisfy the category requirements.
- """
- # If any dice are missing (i.e. still None), return 0.
- if any(d is None for d in dice):
- return 0
- counts = {i: dice.count(i) for i in range(1, 7)}
- total = sum(dice)
- if category == "Ones":
- return counts.get(1, 0) * 1
- elif category == "Twos":
- return counts.get(2, 0) * 2
- elif category == "Threes":
- return counts.get(3, 0) * 3
- elif category == "Fours":
- return counts.get(4, 0) * 4
- elif category == "Fives":
- return counts.get(5, 0) * 5
- elif category == "Sixes":
- return counts.get(6, 0) * 6
- elif category == "Three of a Kind":
- if any(count >= 3 for count in counts.values()):
- return total
- else:
- return 0
- elif category == "Four of a Kind":
- if any(count >= 4 for count in counts.values()):
- return total
- else:
- return 0
- elif category == "Full House":
- # A full house is exactly a 3-of-a-kind and a pair (e.g. 2,2,3,3,3)
- counts_list = sorted([count for count in counts.values() if count > 0])
- if counts_list == [2, 3]:
- return 25
- else:
- return 0
- elif category == "Small Straight":
- # Check for any 4 consecutive numbers.
- unique = set(dice)
- straights = [{1, 2, 3, 4}, {2, 3, 4, 5}, {3, 4, 5, 6}]
- for s in straights:
- if s.issubset(unique):
- return 30
- return 0
- elif category == "Large Straight":
- unique = set(dice)
- if unique == {1, 2, 3, 4, 5} or unique == {2, 3, 4, 5, 6}:
- return 40
- return 0
- elif category == "Yahtzee":
- if any(count == 5 for count in counts.values()):
- return 50
- else:
- return 0
- elif category == "Chance":
- return total
- return 0
- def choose_score(self, category):
- """
- Called when a player selects a scoring category.
- The function computes the score for the current dice,
- records it for the current player, updates the scoreboard,
- and then moves to the next player's turn.
- """
- score = self.compute_score(self.dice, category)
- current_player = self.players[self.current_player_index]
- current_player["scores"][category] = score
- messagebox.showinfo("Score Recorded",
- f"{current_player['name']} scored {score} in {category}")
- self.turns_taken[self.current_player_index] += 1
- self.update_score_buttons_state()
- self.update_scoreboard()
- # Check if the game is over (all 13 turns taken by both players)
- if self.check_game_over():
- self.end_game()
- else:
- self.next_turn()
- def update_scoreboard(self):
- """Refreshes the scoreboard display."""
- self.scoreboard_label.config(text=self.get_scoreboard_text())
- def get_scoreboard_text(self):
- """Builds a text string showing each category and both players’ scores."""
- text = "Scoreboard:\n"
- text += f"{'Category':20s} {'Player 1':>8s} {'Player 2':>8s}\n"
- text += "-" * 40 + "\n"
- for cat in CATEGORIES:
- score1 = self.players[0]["scores"][cat]
- score2 = self.players[1]["scores"][cat]
- score1_text = str(score1) if score1 is not None else "-"
- score2_text = str(score2) if score2 is not None else "-"
- text += f"{cat:20s} {score1_text:>8s} {score2_text:>8s}\n"
- total1 = sum(score for score in self.players[0]["scores"].values() if score is not None)
- total2 = sum(score for score in self.players[1]["scores"].values() if score is not None)
- text += "-" * 40 + "\n"
- text += f"{'Total':20s} {total1:>8d} {total2:>8d}"
- return text
- def next_turn(self):
- """Resets the turn state and switches to the other player."""
- # Switch to the other player
- self.current_player_index = 1 - self.current_player_index
- # Reset the roll count, dice values, and held status
- self.current_roll = 0
- self.dice = [None] * 5
- self.held = [False] * 5
- # Reset dice button display
- for btn in self.dice_buttons:
- btn.config(text="?", relief=tk.RAISED)
- self.update_info_label()
- self.update_score_buttons_state()
- def check_game_over(self):
- """
- The game is over if both players have completed 13 turns
- (i.e. filled all 13 categories).
- """
- return all(turns >= 13 for turns in self.turns_taken)
- def end_game(self):
- """Ends the game and shows the final scores and winner."""
- total1 = sum(score for score in self.players[0]["scores"].values() if score is not None)
- total2 = sum(score for score in self.players[1]["scores"].values() if score is not None)
- if total1 > total2:
- winner = "Player 1 wins!"
- elif total2 > total1:
- winner = "Player 2 wins!"
- else:
- winner = "It's a tie!"
- messagebox.showinfo("Game Over",
- f"Game Over!\nPlayer 1: {total1}\nPlayer 2: {total2}\n{winner}")
- # Disable further interaction by disabling roll, dice, and score buttons
- self.roll_button.config(state=tk.DISABLED)
- for btn in self.score_buttons.values():
- btn.config(state=tk.DISABLED)
- for btn in self.dice_buttons:
- btn.config(state=tk.DISABLED)
- if __name__ == "__main__":
- root = tk.Tk()
- game = YahtzeeGameGUI(root)
- root.mainloop()
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement