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.
This commit is contained in:
119
backend/main.py
Normal file
119
backend/main.py
Normal file
@@ -0,0 +1,119 @@
|
||||
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)
|
||||
Reference in New Issue
Block a user