add start and stats screen

This commit is contained in:
Maik 2026-06-13 22:10:39 +02:00
parent 4b1f5d9874
commit a1b5805727
4 changed files with 238 additions and 25 deletions

View File

@ -82,6 +82,17 @@ class ActionButtons:
self.next_round_button.pack(pady=10) self.next_round_button.pack(pady=10)
self.next_round_button.configure(state="disabled") self.next_round_button.configure(state="disabled")
self.exit_button = ctk.CTkButton(
self.parent,
text="Exit",
command=self.gui._exit,
fg_color="#555",
text_color="#ccc",
font=("Arial", 16),
hover_color="#666"
)
self.exit_button.pack(pady=(4, 10))
def _enable_action_buttons(self, enable): def _enable_action_buttons(self, enable):
"""Original enable/disable functionality""" """Original enable/disable functionality"""
state = "normal" if enable else "disabled" state = "normal" if enable else "disabled"

View File

@ -53,8 +53,9 @@ class StatsDisplay:
self.stats_window.title("Session Results") self.stats_window.title("Session Results")
self.stats_window.geometry("500x600") self.stats_window.geometry("500x600")
self.stats_window.overrideredirect(True) self.stats_window.overrideredirect(True)
self.stats_window.update_idletasks()
self.stats_window.grab_set() self.stats_window.grab_set()
# Original positioning # Original positioning
stats_x = self.parent.winfo_x() + (self.parent.winfo_width() - 500) // 2 stats_x = self.parent.winfo_x() + (self.parent.winfo_width() - 500) // 2
stats_y = self.parent.winfo_y() + (self.parent.winfo_height() - 600) // 2 stats_y = self.parent.winfo_y() + (self.parent.winfo_height() - 600) // 2
@ -74,6 +75,41 @@ class StatsDisplay:
self.stats_window.protocol("WM_DELETE_WINDOW", self._terminate_app) 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"
).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): def _terminate_app(self):
"""Proper termination handling""" """Proper termination handling"""
if self.overlay and self.overlay.winfo_exists(): if self.overlay and self.overlay.winfo_exists():

View File

@ -1,4 +1,5 @@
import customtkinter as ctk import customtkinter as ctk
from blackjack_game import BlackjackGame
from game_logic import can_split, get_split_decision, get_correct_decision from game_logic import can_split, get_split_decision, get_correct_decision
from .utils import load_card_images from .utils import load_card_images
from .components.cards import CardDisplay from .components.cards import CardDisplay
@ -13,7 +14,7 @@ class BlackjackGUI:
self.card_images = load_card_images() self.card_images = load_card_images()
self._setup_main_window() self._setup_main_window()
self._create_components() self._create_components()
self.start_new_round() self._create_start_screen()
def _setup_main_window(self): def _setup_main_window(self):
"""Initialize main application window""" """Initialize main application window"""
@ -150,9 +151,76 @@ class BlackjackGUI:
self.buttons.next_round_button.configure(state="normal") self.buttons.next_round_button.configure(state="normal")
def _on_main_window_close(self): def _on_main_window_close(self):
"""Handle window close event""" """Handle window close event — just close directly"""
self.app.configure(state="disabled") self.app.destroy()
self.stats._show_final_stats(self.game_logic.get_stats())
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.",
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.buttons.next_round_button.configure(state="disabled")
self.buttons.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): def show_hint(self):
"""Delegate hint display to the HintSystem""" """Delegate hint display to the HintSystem"""
@ -161,4 +229,4 @@ class BlackjackGUI:
def run(self): def run(self):
"""Start application main loop""" """Start application main loop"""
self.app.mainloop() self.app.mainloop()

View File

@ -28,31 +28,69 @@ body{background:#2b2b2b;color:#fff;font-family:Arial,sans-serif;display:flex;jus
#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{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.open{display:flex}
#lightbox img{max-width:90vw;max-height:90vh;border-radius:8px} #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;font-size:16px;border:none;border-radius:6px;padding:8px 24px;cursor:pointer;font-weight:400;margin-top:4px}
#btn-exit:hover{background:#666;color:#fff}
</style> </style>
</head> </head>
<body> <body>
<div id="app"> <div id="app">
<div id="header"> <div id="start-screen">
<span id="streak"></span> <div id="start-content">
<h1>&#x1F0CF; Blackjack Theory Trainer</h1>
<p>This is a blackjack basic strategy trainer and not a real blackjack game. Each 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.</p>
<button id="btn-start">Start Training</button>
</div>
</div> </div>
<div id="table"> <div id="game-screen">
<div id="dealer-section"> <div id="header">
<div class="section-label">Dealer</div> <span id="streak"></span>
<div class="card-row" id="dealer-cards"></div>
</div> </div>
<div id="player-section"> <div id="table">
<div class="section-label">Your Hand</div> <div id="dealer-section">
<div class="card-row" id="player-cards"></div> <div class="section-label">Dealer</div>
<div class="card-row" id="dealer-cards"></div>
</div>
<div id="player-section">
<div class="section-label">Your Hand</div>
<div class="card-row" id="player-cards"></div>
</div>
<div id="actions">
<button class="action-btn" id="btn-hit">Hit</button>
<button class="action-btn" id="btn-stand">Stand</button>
<button class="action-btn" id="btn-double">Double</button>
<button class="action-btn" id="btn-split">Split</button>
</div>
<button id="btn-hint">Hint</button>
<div id="feedback"></div>
<button id="btn-next" disabled>Next Round</button>
<button id="btn-exit">Exit</button>
</div> </div>
<div id="actions"> </div>
<button class="action-btn" id="btn-hit">Hit</button> </div>
<button class="action-btn" id="btn-stand">Stand</button> <div id="stats-modal">
<button class="action-btn" id="btn-double">Double</button> <div id="stats-content">
<button class="action-btn" id="btn-split">Split</button> <h2>Your Statistics</h2>
</div> <div id="stats-data"></div>
<button id="btn-hint">Hint</button> <button id="btn-back-to-start">Back to Start</button>
<div id="feedback"></div>
<button id="btn-next" disabled>Next Round</button>
</div> </div>
</div> </div>
<div id="toast"></div> <div id="toast"></div>
@ -171,7 +209,67 @@ btnNext.addEventListener('click',startRound);
btnHint.addEventListener('click',function(){lightbox.classList.add('open');btnHint.disabled=true}); btnHint.addEventListener('click',function(){lightbox.classList.add('open');btnHint.disabled=true});
lightbox.addEventListener('click',function(){lightbox.classList.remove('open')}); lightbox.addEventListener('click',function(){lightbox.classList.remove('open')});
startRound(); // Screen management
var startScreen=document.getElementById('start-screen');
var gameScreen=document.getElementById('game-screen');
var statsModal=document.getElementById('stats-modal');
var statsData=document.getElementById('stats-data');
var btnStart=document.getElementById('btn-start');
var btnExit=document.getElementById('btn-exit');
var btnBackToStart=document.getElementById('btn-back-to-start');
function showStartScreen(){
startScreen.style.display='flex';
gameScreen.style.display='none';
}
function showGameScreen(){
startScreen.style.display='none';
gameScreen.style.display='block';
startRound();
}
function deleteCookie(name){
document.cookie=name+'=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;';
}
async function showStats(){
try{
var r=await fetch('/api/stats');
if(!r.ok)return;
var s=await r.json();
var total=s.correct_decisions+s.incorrect_decisions;
var acc=total>0?(s.correct_decisions/total*100).toFixed(1):0;
var html='';
html+='<div class="stat-row"><span class="stat-label">Total Rounds</span><span class="stat-value">'+s.total_rounds+'</span></div>';
html+='<div class="stat-row"><span class="stat-label">Accuracy</span><span class="stat-value">'+acc+'%</span></div>';
html+='<div class="stat-row"><span class="stat-label">Current Streak</span><span class="stat-value">'+s.current_streak+'</span></div>';
html+='<div class="stat-row"><span class="stat-label">Longest Streak</span><span class="stat-value">'+s.longest_streak+'</span></div>';
html+='<div class="scenario-header">Scenario Breakdown</div>';
for(var key in s.by_scenario){
var d=s.by_scenario[key];
var t=d.correct+d.incorrect;
var a=t>0?(d.correct/t*100).toFixed(1):0;
var label=key.replace(/_/g,' ').replace(/\b\w/g,function(l){return l.toUpperCase()});
html+='<div class="stat-row"><span class="stat-label">'+label+'</span><span class="stat-value">'+d.correct+'/'+t+' ('+a+'%)</span></div>';
}
statsData.innerHTML=html;
statsModal.classList.add('open');
}catch(e){}
}
function backToStart(){
statsModal.classList.remove('open');
deleteCookie(COOKIE);
showStartScreen();
}
btnStart.addEventListener('click',showGameScreen);
btnExit.addEventListener('click',showStats);
btnBackToStart.addEventListener('click',backToStart);
// Start on the start screen
showStartScreen();
</script> </script>
</body> </body>
</html> </html>