268 lines
9.4 KiB
Python
Executable File
268 lines
9.4 KiB
Python
Executable File
import customtkinter as ctk
|
|
from blackjack_game import BlackjackGame
|
|
from game_logic import can_split, get_split_decision, get_correct_decision
|
|
from .utils import load_card_images
|
|
from .components.cards import CardDisplay
|
|
from .components.buttons import ActionButtons
|
|
from .components.notifications import NotificationSystem
|
|
from .components.hints import HintSystem
|
|
from .components.stats import StatsDisplay
|
|
|
|
class BlackjackGUI:
|
|
def __init__(self, blackjackGame):
|
|
self.game_logic = blackjackGame
|
|
self.card_images = load_card_images()
|
|
self._setup_main_window()
|
|
self._create_components()
|
|
self._create_start_screen()
|
|
|
|
def _setup_main_window(self):
|
|
"""Initialize main application window"""
|
|
self.app = ctk.CTk()
|
|
self.app.title("Blackjack Theory Trainer")
|
|
self.app.geometry("600x650+800+200")
|
|
self.app.protocol("WM_DELETE_WINDOW", self._on_main_window_close)
|
|
ctk.set_appearance_mode("dark")
|
|
|
|
def _create_components(self):
|
|
"""Create all UI components"""
|
|
# Card display
|
|
self.card_display = CardDisplay(self.app, self.card_images)
|
|
|
|
# Action buttons (Hit, Stand, Double, Split)
|
|
self.buttons = ActionButtons(self.app, self)
|
|
|
|
# Hint button
|
|
self.hint_button = ctk.CTkButton(
|
|
self.app,
|
|
text="Hint",
|
|
command=self.show_hint,
|
|
fg_color="white",
|
|
text_color="black",
|
|
font=("Arial", 20)
|
|
)
|
|
self.hint_button.pack(pady=10)
|
|
|
|
# Feedback label (centered — matches web layout)
|
|
self.feedback_label = ctk.CTkLabel(
|
|
self.app,
|
|
text="",
|
|
font=("Arial", 20),
|
|
wraplength=550,
|
|
anchor="center"
|
|
)
|
|
self.feedback_label.pack(pady=20, padx=10, fill="x")
|
|
|
|
# Next Round button
|
|
self.next_round_button = ctk.CTkButton(
|
|
self.app,
|
|
text="Next Round",
|
|
command=self.start_new_round,
|
|
fg_color="white",
|
|
text_color="black",
|
|
font=("Arial", 20)
|
|
)
|
|
self.next_round_button.pack(pady=10)
|
|
self.next_round_button.configure(state="disabled")
|
|
|
|
# Exit button
|
|
self.exit_button = ctk.CTkButton(
|
|
self.app,
|
|
text="Exit",
|
|
command=self._exit,
|
|
fg_color="#555",
|
|
text_color="#ccc",
|
|
font=("Arial", 16),
|
|
hover_color="#666"
|
|
)
|
|
self.exit_button.pack(pady=(4, 10))
|
|
|
|
# Notifications
|
|
self.notifications = NotificationSystem(self.app)
|
|
|
|
# Hint system
|
|
self.hints = HintSystem(self.app)
|
|
self.hints.set_hint_button(self.hint_button)
|
|
|
|
# Stats display
|
|
self.stats = StatsDisplay(self.app)
|
|
|
|
def start_new_round(self):
|
|
"""Start a new round of the game"""
|
|
self._reset_ui()
|
|
self.game_logic.start_new_round()
|
|
self._update_display()
|
|
|
|
def _reset_ui(self):
|
|
"""Reset UI for new round"""
|
|
self.hints._close_hint_window()
|
|
self.hint_button.configure(state="normal")
|
|
self.feedback_label.configure(text="")
|
|
self.next_round_button.configure(state="disabled")
|
|
self.buttons._enable_action_buttons(True)
|
|
self.buttons._reset_button_colors()
|
|
|
|
if hasattr(self.notifications, 'streak_label') and self.notifications.streak_label:
|
|
self.notifications.streak_label.configure(text="")
|
|
|
|
def _update_display(self):
|
|
"""Update all display elements"""
|
|
# Update card display
|
|
self.card_display.update_display(
|
|
self.game_logic.dealer_card,
|
|
self.game_logic.player_hand
|
|
)
|
|
|
|
# Handle blackjack
|
|
if self.game_logic.has_blackjack:
|
|
self.feedback_label.configure(
|
|
text="Blackjack! You win or get a push!",
|
|
text_color="green"
|
|
)
|
|
self.buttons._enable_action_buttons(False)
|
|
self.next_round_button.configure(state="normal")
|
|
|
|
# Handle split possibility
|
|
can_split_bool = can_split(self.game_logic.player_hand)
|
|
if can_split_bool:
|
|
self.buttons.split_button.configure(state="normal")
|
|
self.feedback_label.configure(
|
|
text="You have a pair! Consider splitting.",
|
|
text_color="white"
|
|
)
|
|
else:
|
|
self.buttons.split_button.configure(state="disabled")
|
|
|
|
# Update notifications
|
|
stats = self.game_logic.get_stats()
|
|
self.notifications._update_streak_display(stats["current_streak"])
|
|
if stats.get("pending_streak_notification"):
|
|
self.notifications._show_streak_notification(stats["pending_streak_notification"])
|
|
|
|
def _hit(self):
|
|
self._handle_action("Hit", self.buttons.hit_button)
|
|
|
|
def _stand(self):
|
|
self._handle_action("Stand", self.buttons.stand_button)
|
|
|
|
def _double(self):
|
|
self._handle_action("Double", self.buttons.double_button)
|
|
|
|
def _split(self):
|
|
self._handle_action("Split", self.buttons.split_button)
|
|
|
|
def _handle_action(self, action, button):
|
|
"""Process player actions"""
|
|
# Determine scenario type
|
|
scenario_type = "hard_totals"
|
|
if can_split(self.game_logic.player_hand):
|
|
scenario_type = "pairs"
|
|
elif 'A' in [card.split('_')[-1] for card in self.game_logic.player_hand]:
|
|
scenario_type = "soft_totals"
|
|
|
|
# Check if player can split
|
|
if can_split(self.game_logic.player_hand):
|
|
decision, feedback = get_split_decision(
|
|
self.game_logic.player_hand,
|
|
self.game_logic.dealer_card
|
|
)
|
|
correct = action == decision
|
|
else:
|
|
decision, feedback = get_correct_decision(
|
|
self.game_logic.player_hand,
|
|
self.game_logic.player_total,
|
|
self.game_logic.dealer_card
|
|
)
|
|
correct = action == decision
|
|
|
|
# Update UI
|
|
color = "green" if correct else "red"
|
|
self.feedback_label.configure(text=feedback, text_color=color)
|
|
self.buttons._mark_button(button, correct)
|
|
self.game_logic.update_stats(correct, scenario_type)
|
|
|
|
# Update button states
|
|
self.buttons._enable_action_buttons(False)
|
|
self.next_round_button.configure(state="normal")
|
|
|
|
def _on_main_window_close(self):
|
|
"""Handle window close event — just close directly"""
|
|
self.app.destroy()
|
|
|
|
def _create_start_screen(self):
|
|
"""Create the start screen overlay"""
|
|
if hasattr(self, 'start_frame') and self.start_frame and self.start_frame.winfo_exists():
|
|
return
|
|
self.start_frame = ctk.CTkFrame(self.app, fg_color="#2b2b2b", corner_radius=0)
|
|
self.start_frame.place(relx=0, rely=0, relwidth=1, relheight=1)
|
|
|
|
content = ctk.CTkFrame(self.start_frame, fg_color="transparent")
|
|
content.place(relx=0.5, rely=0.5, anchor="center")
|
|
|
|
ctk.CTkLabel(
|
|
content,
|
|
text="\U0001F0CF Blackjack Theory Trainer",
|
|
font=("Arial", 28, "bold"),
|
|
text_color="white"
|
|
).pack(pady=(0, 20))
|
|
|
|
ctk.CTkLabel(
|
|
content,
|
|
text="This is a blackjack basic strategy trainer and not a real blackjack game.\n\nEach round you're dealt a hand and asked to make the correct play (Hit, Stand, Double, or Split) according to basic strategy. You get immediate feedback and track your accuracy over time.\n\nHave Fun!",
|
|
font=("Arial", 15),
|
|
text_color="#ccc",
|
|
wraplength=450,
|
|
justify="center"
|
|
).pack(pady=(0, 30))
|
|
|
|
ctk.CTkButton(
|
|
content,
|
|
text="Start Training",
|
|
command=self._start_training,
|
|
fg_color="#2aa44f",
|
|
text_color="white",
|
|
font=("Arial", 20, "bold"),
|
|
height=45,
|
|
width=200
|
|
).pack(pady=(0, 10))
|
|
|
|
ctk.CTkButton(
|
|
content,
|
|
text="Quit",
|
|
command=self._on_main_window_close,
|
|
fg_color="#555",
|
|
text_color="#ccc",
|
|
font=("Arial", 16),
|
|
height=35,
|
|
width=120,
|
|
hover_color="#666"
|
|
).pack()
|
|
|
|
def _start_training(self):
|
|
"""Start button handler — enter game mode"""
|
|
if hasattr(self, 'start_frame') and self.start_frame:
|
|
self.start_frame.destroy()
|
|
self.start_frame = None
|
|
self.start_new_round()
|
|
|
|
def _exit(self):
|
|
"""Exit button handler — show stats popup"""
|
|
self.buttons._enable_action_buttons(False)
|
|
self.next_round_button.configure(state="disabled")
|
|
self.hint_button.configure(state="disabled")
|
|
self.stats._show_exit_popup(self.game_logic.get_stats(), self._exit_to_start)
|
|
|
|
def _exit_to_start(self):
|
|
"""Reset game state and return to start screen"""
|
|
self.game_logic = BlackjackGame()
|
|
self._create_start_screen()
|
|
|
|
def show_hint(self):
|
|
"""Delegate hint display to the HintSystem"""
|
|
self.hints._disable_hint_button()
|
|
self.hints.show_hint()
|
|
|
|
def run(self):
|
|
"""Start application main loop"""
|
|
self.app.mainloop()
|