diff --git a/gui/components/buttons.py b/gui/components/buttons.py
index 67f199c..249569d 100755
--- a/gui/components/buttons.py
+++ b/gui/components/buttons.py
@@ -6,7 +6,6 @@ class ActionButtons:
self.gui = gui # Reference to main GUI for callbacks
self._create_button_frame()
self._create_action_buttons()
- self._create_control_buttons()
def _create_button_frame(self):
"""Matches original button frame creation"""
@@ -15,73 +14,16 @@ class ActionButtons:
def _create_action_buttons(self):
"""Replicates original button creation exactly"""
- self.hit_button = ctk.CTkButton(
- self.button_frame,
- text="Hit",
- command=self.gui._hit,
- fg_color="white",
- text_color="black",
- text_color_disabled="white",
- font=("Arial", 20)
- )
+ common = dict(fg_color="white", hover_color="white", text_color="black", text_color_disabled="white", font=("Arial", 20))
+ self.hit_button = ctk.CTkButton(self.button_frame, text="Hit", command=self.gui._hit, **common)
self.hit_button.grid(row=0, column=0, padx=10, pady=10)
-
- self.stand_button = ctk.CTkButton(
- self.button_frame,
- text="Stand",
- command=self.gui._stand,
- fg_color="white",
- text_color="black",
- text_color_disabled="white",
- font=("Arial", 20)
- )
+ self.stand_button = ctk.CTkButton(self.button_frame, text="Stand", command=self.gui._stand, **common)
self.stand_button.grid(row=0, column=1, padx=10, pady=10)
-
- self.double_button = ctk.CTkButton(
- self.button_frame,
- text="Double",
- command=self.gui._double,
- fg_color="white",
- text_color="black",
- text_color_disabled="white",
- font=("Arial", 20)
- )
+ self.double_button = ctk.CTkButton(self.button_frame, text="Double", command=self.gui._double, **common)
self.double_button.grid(row=1, column=0, padx=10, pady=10)
-
- self.split_button = ctk.CTkButton(
- self.button_frame,
- text="Split",
- command=self.gui._split,
- fg_color="white",
- text_color="black",
- text_color_disabled="white",
- font=("Arial", 20)
- )
+ self.split_button = ctk.CTkButton(self.button_frame, text="Split", command=self.gui._split, **common)
self.split_button.grid(row=1, column=1, padx=10, pady=10)
- def _create_control_buttons(self):
- """Replicates hint and next round buttons"""
- self.hint_button = ctk.CTkButton(
- self.parent,
- text="Hint",
- command=self.gui.show_hint,
- fg_color="white",
- text_color="black",
- font=("Arial", 20)
- )
- self.hint_button.pack(pady=10)
-
- self.next_round_button = ctk.CTkButton(
- self.parent,
- text="Next Round",
- command=self.gui.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")
-
def _enable_action_buttons(self, enable):
"""Original enable/disable functionality"""
state = "normal" if enable else "disabled"
@@ -92,12 +34,10 @@ class ActionButtons:
def _reset_button_colors(self):
"""Original color reset"""
- self.hit_button.configure(fg_color="white")
- self.stand_button.configure(fg_color="white")
- self.double_button.configure(fg_color="white")
- self.split_button.configure(fg_color="white")
+ for b in (self.hit_button, self.stand_button, self.double_button, self.split_button):
+ b.configure(fg_color="white", hover_color="white")
def _mark_button(self, button, is_correct):
"""Original button marking"""
color = "green" if is_correct else "red"
- button.configure(fg_color=color)
\ No newline at end of file
+ button.configure(fg_color=color, hover_color=color)
\ No newline at end of file
diff --git a/gui/components/stats.py b/gui/components/stats.py
index c0d4025..aa57011 100755
--- a/gui/components/stats.py
+++ b/gui/components/stats.py
@@ -53,8 +53,9 @@ class StatsDisplay:
self.stats_window.title("Session Results")
self.stats_window.geometry("500x600")
self.stats_window.overrideredirect(True)
+ self.stats_window.update_idletasks()
self.stats_window.grab_set()
-
+
# Original positioning
stats_x = self.parent.winfo_x() + (self.parent.winfo_width() - 500) // 2
stats_y = self.parent.winfo_y() + (self.parent.winfo_height() - 600) // 2
@@ -69,11 +70,48 @@ class StatsDisplay:
command=self._terminate_app,
font=("Arial", 16),
height=40,
- fg_color="#2aa44f" # Professional green
+ fg_color="#2aa44f",
+ hover_color="#238c44"
).pack(pady=20, side="bottom", anchor="center")
self.stats_window.protocol("WM_DELETE_WINDOW", self._terminate_app)
+ def _show_exit_popup(self, stats, on_back_to_start):
+ """Exit stats popup with back-to-start button"""
+ self.overlay = ctk.CTkFrame(self.parent, fg_color="black")
+ self.overlay.place(relwidth=1, relheight=1)
+
+ self.stats_window = ctk.CTkToplevel(self.parent)
+ self.stats_window.title("Session Results")
+ self.stats_window.geometry("500x600")
+ self.stats_window.overrideredirect(True)
+ self.stats_window.update_idletasks()
+ self.stats_window.grab_set()
+
+ stats_x = self.parent.winfo_x() + (self.parent.winfo_width() - 500) // 2
+ stats_y = self.parent.winfo_y() + (self.parent.winfo_height() - 600) // 2
+ self.stats_window.geometry(f"+{stats_x}+{stats_y}")
+
+ self._load_stats_content(self.stats_window, stats)
+
+ ctk.CTkButton(
+ self.stats_window,
+ text="Back to Start",
+ command=lambda: self._close_exit_popup(on_back_to_start),
+ font=("Arial", 16),
+ height=40,
+ fg_color="#2aa44f",
+ hover_color="#238c44"
+ ).pack(pady=20, side="bottom", anchor="center")
+
+ def _close_exit_popup(self, callback):
+ """Close the exit popup and trigger callback"""
+ if self.overlay and self.overlay.winfo_exists():
+ self.overlay.destroy()
+ if hasattr(self, 'stats_window') and self.stats_window and self.stats_window.winfo_exists():
+ self.stats_window.destroy()
+ callback()
+
def _terminate_app(self):
"""Proper termination handling"""
if self.overlay and self.overlay.winfo_exists():
diff --git a/gui/gui.py b/gui/gui.py
index 579720a..e84dcd0 100755
--- a/gui/gui.py
+++ b/gui/gui.py
@@ -1,4 +1,5 @@
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
@@ -13,7 +14,7 @@ class BlackjackGUI:
self.card_images = load_card_images()
self._setup_main_window()
self._create_components()
- self.start_new_round()
+ self._create_start_screen()
def _setup_main_window(self):
"""Initialize main application window"""
@@ -27,30 +28,67 @@ class BlackjackGUI:
"""Create all UI components"""
# Card display
self.card_display = CardDisplay(self.app, self.card_images)
-
- # Buttons (pass reference to self for callbacks)
+
+ # Action buttons (Hit, Stand, Double, Split)
self.buttons = ActionButtons(self.app, self)
-
- # Notifications
- self.notifications = NotificationSystem(self.app)
-
- # Hint system
- self.hints = HintSystem(self.app)
- self.hints.set_hint_button(self.buttons.hint_button)
-
- # Stats display
- self.stats = StatsDisplay(self.app)
-
- # Feedback label (only remaining widget in main file)
+
+ # Hint button
+ self.hint_button = ctk.CTkButton(
+ self.app,
+ text="Hint",
+ command=self.show_hint,
+ fg_color="white",
+ hover_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="w"
+ 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",
+ hover_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()
@@ -60,11 +98,12 @@ class BlackjackGUI:
def _reset_ui(self):
"""Reset UI for new round"""
self.hints._close_hint_window()
- self.buttons.hint_button.configure(state="normal")
+ self.hint_button.configure(state="normal")
self.feedback_label.configure(text="")
- self.buttons.next_round_button.configure(state="disabled")
+ self.next_round_button.configure(state="disabled")
self.buttons._enable_action_buttons(True)
self.buttons._reset_button_colors()
+ self.buttons.split_button.configure(text="")
if hasattr(self.notifications, 'streak_label') and self.notifications.streak_label:
self.notifications.streak_label.configure(text="")
@@ -84,18 +123,18 @@ class BlackjackGUI:
text_color="green"
)
self.buttons._enable_action_buttons(False)
- self.buttons.next_round_button.configure(state="normal")
+ 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.buttons.split_button.configure(state="normal", text="Split")
self.feedback_label.configure(
text="You have a pair! Consider splitting.",
text_color="white"
)
else:
- self.buttons.split_button.configure(state="disabled")
+ self.buttons.split_button.configure(state="disabled", text="")
# Update notifications
stats = self.game_logic.get_stats()
@@ -147,12 +186,80 @@ class BlackjackGUI:
# Update button states
self.buttons._enable_action_buttons(False)
- self.buttons.next_round_button.configure(state="normal")
+ self.next_round_button.configure(state="normal")
def _on_main_window_close(self):
- """Handle window close event"""
- self.app.configure(state="disabled")
- self.stats._show_final_stats(self.game_logic.get_stats())
+ """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",
+ hover_color="#238c44",
+ 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"""
@@ -161,4 +268,4 @@ class BlackjackGUI:
def run(self):
"""Start application main loop"""
- self.app.mainloop()
\ No newline at end of file
+ self.app.mainloop()
diff --git a/static/index.html b/static/index.html
index 6f6ee95..3f36ff3 100644
--- a/static/index.html
+++ b/static/index.html
@@ -18,41 +18,78 @@ body{background:#2b2b2b;color:#fff;font-family:Arial,sans-serif;display:flex;jus
#actions{display:grid;grid-template-columns:1fr 1fr;gap:10px;width:100%;max-width:340px;margin:16px 0 8px}
.action-btn{padding:12px;font-size:20px;border:none;border-radius:8px;cursor:pointer;background:#fff;color:#000;font-weight:700;transition:background .15s,color .15s}
.action-btn:disabled{background:#555;color:#999;cursor:not-allowed}
-#btn-hint{background:#fff;color:#000;font-size:20px;border:none;border-radius:8px;padding:12px 40px;cursor:pointer;margin:8px 0;font-weight:700}
-#btn-hint:disabled{background:#555;color:#999;cursor:not-allowed}
+.btn-util{background:#fff;color:#000;font-size:20px;border:none;border-radius:8px;padding:12px 20px;cursor:pointer;margin:8px 0;font-weight:700;width:220px}
+.btn-util:disabled{background:#555;color:#999;cursor:not-allowed}
#feedback{font-size:20px;text-align:center;min-height:60px;padding:10px;width:100%;max-width:550px;line-height:1.4;word-wrap:break-word}
-#btn-next{background:#fff;color:#000;font-size:20px;border:none;border-radius:8px;padding:12px 40px;cursor:pointer;margin:8px 0;font-weight:700}
-#btn-next:disabled{background:#555;color:#999;cursor:not-allowed}
#toast{position:fixed;top:20px;right:20px;background:#3a7ebf;color:#fff;padding:12px 24px;border-radius:8px;font-size:16px;font-weight:700;opacity:0;transform:translateY(-20px);transition:opacity .3s,transform .3s;pointer-events:none;z-index:1000}
#toast.show{opacity:1;transform:translateY(0)}
#lightbox{display:none;position:fixed;inset:0;background:rgba(0,0,0,.85);justify-content:center;align-items:center;z-index:999;cursor:pointer}
#lightbox.open{display:flex}
#lightbox img{max-width:90vw;max-height:90vh;border-radius:8px}
+#start-screen{display:flex;justify-content:center;align-items:center;min-height:80vh;width:100%}
+#start-content{text-align:center;max-width:500px}
+#start-content h1{font-size:32px;margin-bottom:24px;color:#fff}
+#start-content p{font-size:16px;line-height:1.6;color:#ccc;margin-bottom:32px}
+#btn-start{background:#2aa44f;color:#fff;font-size:22px;border:none;border-radius:8px;padding:14px 48px;cursor:pointer;font-weight:700}
+#btn-start:hover{background:#238c44}
+#game-screen{width:100%}
+#stats-modal{display:none;position:fixed;inset:0;background:rgba(0,0,0,.85);justify-content:center;align-items:center;z-index:999}
+#stats-modal.open{display:flex}
+#stats-content{background:#2b2b2b;padding:32px;border-radius:12px;max-width:450px;width:90%;text-align:center}
+#stats-content h2{font-size:24px;margin-bottom:20px;color:#fff}
+#stats-data{text-align:left;margin-bottom:24px}
+.stat-row{display:flex;justify-content:space-between;padding:6px 0;font-size:16px;color:#ccc;border-bottom:1px solid #444}
+.stat-row:last-child{border-bottom:none}
+.stat-label{color:#aaa}
+.stat-value{color:#fff;font-weight:700}
+.scenario-header{font-size:16px;font-weight:700;color:#fff;margin-top:16px;margin-bottom:8px}
+#btn-back-to-start{background:#2aa44f;color:#fff;font-size:18px;border:none;border-radius:8px;padding:12px 36px;cursor:pointer;font-weight:700;margin-top:8px}
+#btn-back-to-start:hover{background:#238c44}
+#btn-exit{background:#555;color:#ccc;margin-top:4px}
-