116 lines
3.6 KiB
Python
116 lines
3.6 KiB
Python
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")
|