Fam Zheng e465b1cf71 Initial commit: Simple ASM - ARM assembly learning game
10-level progressive game teaching ARM assembly basics:
registers, arithmetic, bitwise ops, memory, branching, loops.
Vue 3 + FastAPI + SQLite with K8s deployment.
2026-04-07 10:17:15 +01:00

120 lines
3.2 KiB
Python

import os
from contextlib import asynccontextmanager
from fastapi import FastAPI, HTTPException
from fastapi.responses import FileResponse
from fastapi.staticfiles import StaticFiles
from pydantic import BaseModel
from .database import get_db, init_db
FRONTEND_DIR = os.environ.get("FRONTEND_DIR", "../frontend/dist")
@asynccontextmanager
async def lifespan(app: FastAPI):
await init_db()
yield
app = FastAPI(lifespan=lifespan)
class PlayerCreate(BaseModel):
name: str
class ProgressSave(BaseModel):
player_id: int
level_id: int
stars: int
code: str = ""
@app.get("/api/health")
async def health():
return {"status": "ok"}
@app.post("/api/players")
async def create_or_get_player(data: PlayerCreate):
name = data.name.strip()
if not name:
raise HTTPException(400, "名字不能为空")
db = await get_db()
try:
row = await db.execute_fetchall(
"SELECT id, name FROM players WHERE name = ?", (name,)
)
if row:
player_id = row[0][0]
else:
cursor = await db.execute(
"INSERT INTO players (name) VALUES (?)", (name,)
)
await db.commit()
player_id = cursor.lastrowid
progress = await db.execute_fetchall(
"SELECT level_id, stars, code FROM progress WHERE player_id = ?",
(player_id,),
)
progress_dict = {
r[0]: {"stars": r[1], "code": r[2], "completed": True}
for r in progress
}
return {"id": player_id, "name": name, "progress": progress_dict}
finally:
await db.close()
@app.post("/api/progress")
async def save_progress(data: ProgressSave):
db = await get_db()
try:
await db.execute(
"""INSERT INTO progress (player_id, level_id, stars, code)
VALUES (?, ?, ?, ?)
ON CONFLICT (player_id, level_id)
DO UPDATE SET stars = MAX(stars, excluded.stars),
code = excluded.code,
completed_at = CURRENT_TIMESTAMP""",
(data.player_id, data.level_id, data.stars, data.code),
)
await db.commit()
return {"success": True}
finally:
await db.close()
@app.get("/api/leaderboard")
async def leaderboard():
db = await get_db()
try:
rows = await db.execute_fetchall("""
SELECT p.name, COALESCE(SUM(pr.stars), 0) as total_stars,
COUNT(pr.id) as levels_completed
FROM players p
LEFT JOIN progress pr ON p.id = pr.player_id
GROUP BY p.id
ORDER BY total_stars DESC, levels_completed DESC
LIMIT 50
""")
return [
{"name": r[0], "total_stars": r[1], "levels_completed": r[2]}
for r in rows
]
finally:
await db.close()
# Serve frontend static files
if os.path.isdir(FRONTEND_DIR):
@app.get("/{full_path:path}")
async def serve_spa(full_path: str):
file_path = os.path.join(FRONTEND_DIR, full_path)
if full_path and os.path.isfile(file_path):
return FileResponse(file_path)
index = os.path.join(FRONTEND_DIR, "index.html")
return FileResponse(index)