import uuid from typing import Optional from fastapi import FastAPI, Cookie, HTTPException from fastapi.responses import FileResponse, JSONResponse from fastapi.staticfiles import StaticFiles from pydantic import BaseModel from blackjack_game import BlackjackGame from game_logic import can_split, get_correct_decision, get_split_decision app = FastAPI(title="Blackjack Theory Trainer") sessions: dict[str, BlackjackGame] = {} def get_or_create_session(session_id: Optional[str] = None) -> tuple[str, BlackjackGame, bool]: if session_id and session_id in sessions: return session_id, sessions[session_id], False new_id = str(uuid.uuid4()) game = BlackjackGame() sessions[new_id] = game return new_id, game, True class DecisionRequest(BaseModel): action: str @app.post("/api/start-round") async def start_round(session_id: Optional[str] = Cookie(None)): sid, game, is_new = get_or_create_session(session_id) game.start_new_round() data = { "dealer_card": game.dealer_card, "player_hand": game.player_hand, "player_total": game.player_total, "has_blackjack": game.has_blackjack, "can_split": can_split(game.player_hand), "current_streak": game.stats["current_streak"], } response = JSONResponse(content=data) if is_new: response.set_cookie(key="session_id", value=sid, path="/") return response @app.post("/api/check-decision") async def check_decision(req: DecisionRequest, session_id: Optional[str] = Cookie(None)): if not session_id or session_id not in sessions: raise HTTPException(status_code=400, detail="No active session") game = sessions[session_id] scenario_type = "hard_totals" if can_split(game.player_hand): scenario_type = "pairs" elif "A" in [c.split("_")[-1] for c in game.player_hand]: scenario_type = "soft_totals" if can_split(game.player_hand): decision, feedback = get_split_decision(game.player_hand, game.dealer_card) correct = req.action == decision else: decision, feedback = get_correct_decision( game.player_hand, game.player_total, game.dealer_card ) correct = req.action == decision had_streak = game.stats["current_streak"] game.update_stats(correct, scenario_type) stats = game.get_stats() streak_ended = None if not correct and had_streak >= 3: streak_ended = f"Streak ended at {had_streak}!" return { "correct": correct, "decision": decision, "feedback": feedback, "scenario_type": scenario_type, "stats": stats, "streak_ended": streak_ended, } @app.get("/api/stats") async def get_stats(session_id: Optional[str] = Cookie(None)): if not session_id or session_id not in sessions: raise HTTPException(status_code=400, detail="No active session") return sessions[session_id].get_stats() @app.get("/api/hint") async def get_hint(session_id: Optional[str] = Cookie(None)): if not session_id or session_id not in sessions: raise HTTPException(status_code=400, detail="No active session") game = sessions[session_id] if can_split(game.player_hand): decision, feedback = get_split_decision(game.player_hand, game.dealer_card) else: decision, feedback = get_correct_decision( game.player_hand, game.player_total, game.dealer_card ) return {"decision": decision, "feedback": feedback} @app.get("/") async def read_index(): return FileResponse("static/index.html") app.mount("/images", StaticFiles(directory="images"), name="images")