Spaces:
Runtime error
Runtime error
Add nice tut
Browse files
backend/config.py
CHANGED
|
@@ -3,6 +3,8 @@ from dotenv import load_dotenv
|
|
| 3 |
|
| 4 |
load_dotenv()
|
| 5 |
|
|
|
|
|
|
|
| 6 |
MISTRAL_API_KEY: str = os.getenv("MISTRAL_API_KEY", "")
|
| 7 |
SECRET_KEY: str = os.getenv("SECRET_KEY", "dev-secret-key-change-me")
|
| 8 |
|
|
|
|
| 3 |
|
| 4 |
load_dotenv()
|
| 5 |
|
| 6 |
+
DEBUG: bool = os.getenv("DEBUG", "false").lower() == "true"
|
| 7 |
+
|
| 8 |
MISTRAL_API_KEY: str = os.getenv("MISTRAL_API_KEY", "")
|
| 9 |
SECRET_KEY: str = os.getenv("SECRET_KEY", "dev-secret-key-change-me")
|
| 10 |
|
backend/main.py
CHANGED
|
@@ -44,7 +44,7 @@ from pathlib import Path
|
|
| 44 |
from typing import Any, Optional
|
| 45 |
|
| 46 |
import socketio
|
| 47 |
-
from fastapi import APIRouter, Body, FastAPI, HTTPException
|
| 48 |
from fastapi.middleware.cors import CORSMiddleware
|
| 49 |
from fastapi.staticfiles import StaticFiles
|
| 50 |
from fastapi.responses import FileResponse
|
|
@@ -56,8 +56,15 @@ from game.state import GameState, TUTORIAL_DUMMY_ID
|
|
| 56 |
from lobby.manager import LobbyManager
|
| 57 |
from lobby.safe_name import sanitize_player_name
|
| 58 |
from voice import command_parser, stt
|
|
|
|
| 59 |
import stats as _stats
|
| 60 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 61 |
BOT_OFFER_DELAY = 10 # seconds before offering bot opponent
|
| 62 |
|
| 63 |
logging.basicConfig(level=logging.INFO)
|
|
@@ -180,6 +187,10 @@ def get_stats_recent():
|
|
| 180 |
|
| 181 |
fastapi_app.include_router(_stats_router)
|
| 182 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 183 |
# Unit sound effects (generated by scripts/generate_unit_sounds.py)
|
| 184 |
_SOUNDS_DIR = Path(__file__).parent / "static" / "sounds"
|
| 185 |
_UNITS_SOUNDS_DIR = _SOUNDS_DIR / "units"
|
|
@@ -290,7 +301,7 @@ def _merged_walkable_polygon(polygons: list) -> list:
|
|
| 290 |
return polygons[0]
|
| 291 |
|
| 292 |
|
| 293 |
-
@fastapi_app.put("/api/map/walkable")
|
| 294 |
async def save_walkable_polygon(body: dict = Body(...)):
|
| 295 |
"""Enregistre les polygones marchables (liste de polygones [x, y] en 0-100). Le jeu reçoit la fusion via GET."""
|
| 296 |
polygons = body.get("polygons")
|
|
@@ -353,7 +364,7 @@ def _generate_geysers_around(cx: int, cy: int, count: int = 1, radius: float = 4
|
|
| 353 |
return out
|
| 354 |
|
| 355 |
|
| 356 |
-
@fastapi_app.put("/api/map/positions")
|
| 357 |
async def save_map_positions(body: dict = Body(...)):
|
| 358 |
"""Enregistre positions de départ (2 ou 3, si 3 alors 2 seront tirées au sort par partie) et expansions."""
|
| 359 |
import json
|
|
@@ -409,7 +420,7 @@ async def save_map_positions(body: dict = Body(...)):
|
|
| 409 |
return {"status": "ok", "minerals_count": total_minerals, "geysers_count": total_geysers}
|
| 410 |
|
| 411 |
|
| 412 |
-
@fastapi_app.put("/api/map/locations")
|
| 413 |
async def save_map_locations(body: dict = Body(...)):
|
| 414 |
"""Enregistre les zones clés (locations) dans map.json."""
|
| 415 |
import json
|
|
@@ -451,7 +462,7 @@ async def list_unit_sounds():
|
|
| 451 |
return {"sounds": sounds}
|
| 452 |
|
| 453 |
|
| 454 |
-
@fastapi_app.delete("/api/sounds/units/{unit}/{kind}")
|
| 455 |
async def delete_unit_sound(unit: str, kind: str):
|
| 456 |
"""Supprime un son. Relancer le script régénère uniquement les manquants."""
|
| 457 |
if not unit.replace("-", "").isalnum() or not kind.replace("-", "").isalnum():
|
|
@@ -485,7 +496,7 @@ async def list_building_sprites():
|
|
| 485 |
return {"sprites": _list_sprites(_SPRITES_DIR / "buildings")}
|
| 486 |
|
| 487 |
|
| 488 |
-
@fastapi_app.post("/api/sprites/generate")
|
| 489 |
async def generate_sprites():
|
| 490 |
"""Lance la génération des sprites (unités + bâtiments) via l'API Mistral. Peut prendre plusieurs minutes."""
|
| 491 |
import subprocess
|
|
@@ -522,7 +533,7 @@ def _run_sprite_script(extra_args: list[str]) -> subprocess.CompletedProcess:
|
|
| 522 |
)
|
| 523 |
|
| 524 |
|
| 525 |
-
@fastapi_app.post("/api/sprites/generate/units/{unit_id}")
|
| 526 |
async def generate_one_unit_sprite(unit_id: str):
|
| 527 |
"""Régénère une seule image d'unité."""
|
| 528 |
unit_id = unit_id.strip().lower()
|
|
@@ -536,7 +547,7 @@ async def generate_one_unit_sprite(unit_id: str):
|
|
| 536 |
return {"status": "ok", "id": unit_id}
|
| 537 |
|
| 538 |
|
| 539 |
-
@fastapi_app.post("/api/sprites/generate/buildings/{building_id}")
|
| 540 |
async def generate_one_building_sprite(building_id: str):
|
| 541 |
"""Régénère une seule image de bâtiment."""
|
| 542 |
building_id = building_id.strip().lower().replace(" ", "_")
|
|
@@ -556,7 +567,7 @@ async def list_resource_sprites():
|
|
| 556 |
return {"sprites": _list_sprites(_SPRITES_DIR / "resources")}
|
| 557 |
|
| 558 |
|
| 559 |
-
@fastapi_app.post("/api/sprites/generate/resources/{resource_id}")
|
| 560 |
async def generate_one_resource_sprite(resource_id: str):
|
| 561 |
"""Régénère une seule icône de ressource (mineral, geyser)."""
|
| 562 |
resource_id = resource_id.strip().lower()
|
|
@@ -579,7 +590,7 @@ async def list_icons():
|
|
| 579 |
return {"icons": _list_sprites(_ICONS_DIR)}
|
| 580 |
|
| 581 |
|
| 582 |
-
@fastapi_app.post("/api/sprites/rotate/{kind}/{sprite_id}")
|
| 583 |
async def rotate_sprite(kind: str, sprite_id: str):
|
| 584 |
"""Fait pivoter un sprite de 90° dans le sens antihoraire (rotate CCW = tourne l'arme vers le haut)."""
|
| 585 |
valid_kinds = {"units": _SPRITES_DIR / "units", "buildings": _SPRITES_DIR / "buildings", "resources": _SPRITES_DIR / "resources"}
|
|
@@ -600,7 +611,7 @@ async def rotate_sprite(kind: str, sprite_id: str):
|
|
| 600 |
return {"status": "ok", "id": sprite_id, "kind": kind}
|
| 601 |
|
| 602 |
|
| 603 |
-
@fastapi_app.post("/api/icons/rotate/{icon_id}")
|
| 604 |
async def rotate_icon(icon_id: str):
|
| 605 |
"""Fait pivoter une icône UI de 90° dans le sens antihoraire."""
|
| 606 |
icon_id = icon_id.strip().lower()
|
|
@@ -617,7 +628,7 @@ async def rotate_icon(icon_id: str):
|
|
| 617 |
return {"status": "ok", "id": icon_id}
|
| 618 |
|
| 619 |
|
| 620 |
-
@fastapi_app.post("/api/icons/generate/{icon_id}")
|
| 621 |
async def generate_one_icon(icon_id: str):
|
| 622 |
"""Génère une icône UI symbolique (mineral, gas, supply)."""
|
| 623 |
icon_id = icon_id.strip().lower()
|
|
|
|
| 44 |
from typing import Any, Optional
|
| 45 |
|
| 46 |
import socketio
|
| 47 |
+
from fastapi import APIRouter, Body, Depends, FastAPI, HTTPException
|
| 48 |
from fastapi.middleware.cors import CORSMiddleware
|
| 49 |
from fastapi.staticfiles import StaticFiles
|
| 50 |
from fastapi.responses import FileResponse
|
|
|
|
| 56 |
from lobby.manager import LobbyManager
|
| 57 |
from lobby.safe_name import sanitize_player_name
|
| 58 |
from voice import command_parser, stt
|
| 59 |
+
import config as _config
|
| 60 |
import stats as _stats
|
| 61 |
|
| 62 |
+
|
| 63 |
+
def require_debug():
|
| 64 |
+
"""FastAPI dependency — raises 403 in production (DEBUG=False)."""
|
| 65 |
+
if not _config.DEBUG:
|
| 66 |
+
raise HTTPException(status_code=403, detail="Not available in production")
|
| 67 |
+
|
| 68 |
BOT_OFFER_DELAY = 10 # seconds before offering bot opponent
|
| 69 |
|
| 70 |
logging.basicConfig(level=logging.INFO)
|
|
|
|
| 187 |
|
| 188 |
fastapi_app.include_router(_stats_router)
|
| 189 |
|
| 190 |
+
@fastapi_app.get("/api/config")
|
| 191 |
+
def get_config():
|
| 192 |
+
return {"debug": _config.DEBUG}
|
| 193 |
+
|
| 194 |
# Unit sound effects (generated by scripts/generate_unit_sounds.py)
|
| 195 |
_SOUNDS_DIR = Path(__file__).parent / "static" / "sounds"
|
| 196 |
_UNITS_SOUNDS_DIR = _SOUNDS_DIR / "units"
|
|
|
|
| 301 |
return polygons[0]
|
| 302 |
|
| 303 |
|
| 304 |
+
@fastapi_app.put("/api/map/walkable", dependencies=[Depends(require_debug)])
|
| 305 |
async def save_walkable_polygon(body: dict = Body(...)):
|
| 306 |
"""Enregistre les polygones marchables (liste de polygones [x, y] en 0-100). Le jeu reçoit la fusion via GET."""
|
| 307 |
polygons = body.get("polygons")
|
|
|
|
| 364 |
return out
|
| 365 |
|
| 366 |
|
| 367 |
+
@fastapi_app.put("/api/map/positions", dependencies=[Depends(require_debug)])
|
| 368 |
async def save_map_positions(body: dict = Body(...)):
|
| 369 |
"""Enregistre positions de départ (2 ou 3, si 3 alors 2 seront tirées au sort par partie) et expansions."""
|
| 370 |
import json
|
|
|
|
| 420 |
return {"status": "ok", "minerals_count": total_minerals, "geysers_count": total_geysers}
|
| 421 |
|
| 422 |
|
| 423 |
+
@fastapi_app.put("/api/map/locations", dependencies=[Depends(require_debug)])
|
| 424 |
async def save_map_locations(body: dict = Body(...)):
|
| 425 |
"""Enregistre les zones clés (locations) dans map.json."""
|
| 426 |
import json
|
|
|
|
| 462 |
return {"sounds": sounds}
|
| 463 |
|
| 464 |
|
| 465 |
+
@fastapi_app.delete("/api/sounds/units/{unit}/{kind}", dependencies=[Depends(require_debug)])
|
| 466 |
async def delete_unit_sound(unit: str, kind: str):
|
| 467 |
"""Supprime un son. Relancer le script régénère uniquement les manquants."""
|
| 468 |
if not unit.replace("-", "").isalnum() or not kind.replace("-", "").isalnum():
|
|
|
|
| 496 |
return {"sprites": _list_sprites(_SPRITES_DIR / "buildings")}
|
| 497 |
|
| 498 |
|
| 499 |
+
@fastapi_app.post("/api/sprites/generate", dependencies=[Depends(require_debug)])
|
| 500 |
async def generate_sprites():
|
| 501 |
"""Lance la génération des sprites (unités + bâtiments) via l'API Mistral. Peut prendre plusieurs minutes."""
|
| 502 |
import subprocess
|
|
|
|
| 533 |
)
|
| 534 |
|
| 535 |
|
| 536 |
+
@fastapi_app.post("/api/sprites/generate/units/{unit_id}", dependencies=[Depends(require_debug)])
|
| 537 |
async def generate_one_unit_sprite(unit_id: str):
|
| 538 |
"""Régénère une seule image d'unité."""
|
| 539 |
unit_id = unit_id.strip().lower()
|
|
|
|
| 547 |
return {"status": "ok", "id": unit_id}
|
| 548 |
|
| 549 |
|
| 550 |
+
@fastapi_app.post("/api/sprites/generate/buildings/{building_id}", dependencies=[Depends(require_debug)])
|
| 551 |
async def generate_one_building_sprite(building_id: str):
|
| 552 |
"""Régénère une seule image de bâtiment."""
|
| 553 |
building_id = building_id.strip().lower().replace(" ", "_")
|
|
|
|
| 567 |
return {"sprites": _list_sprites(_SPRITES_DIR / "resources")}
|
| 568 |
|
| 569 |
|
| 570 |
+
@fastapi_app.post("/api/sprites/generate/resources/{resource_id}", dependencies=[Depends(require_debug)])
|
| 571 |
async def generate_one_resource_sprite(resource_id: str):
|
| 572 |
"""Régénère une seule icône de ressource (mineral, geyser)."""
|
| 573 |
resource_id = resource_id.strip().lower()
|
|
|
|
| 590 |
return {"icons": _list_sprites(_ICONS_DIR)}
|
| 591 |
|
| 592 |
|
| 593 |
+
@fastapi_app.post("/api/sprites/rotate/{kind}/{sprite_id}", dependencies=[Depends(require_debug)])
|
| 594 |
async def rotate_sprite(kind: str, sprite_id: str):
|
| 595 |
"""Fait pivoter un sprite de 90° dans le sens antihoraire (rotate CCW = tourne l'arme vers le haut)."""
|
| 596 |
valid_kinds = {"units": _SPRITES_DIR / "units", "buildings": _SPRITES_DIR / "buildings", "resources": _SPRITES_DIR / "resources"}
|
|
|
|
| 611 |
return {"status": "ok", "id": sprite_id, "kind": kind}
|
| 612 |
|
| 613 |
|
| 614 |
+
@fastapi_app.post("/api/icons/rotate/{icon_id}", dependencies=[Depends(require_debug)])
|
| 615 |
async def rotate_icon(icon_id: str):
|
| 616 |
"""Fait pivoter une icône UI de 90° dans le sens antihoraire."""
|
| 617 |
icon_id = icon_id.strip().lower()
|
|
|
|
| 628 |
return {"status": "ok", "id": icon_id}
|
| 629 |
|
| 630 |
|
| 631 |
+
@fastapi_app.post("/api/icons/generate/{icon_id}", dependencies=[Depends(require_debug)])
|
| 632 |
async def generate_one_icon(icon_id: str):
|
| 633 |
"""Génère une icône UI symbolique (mineral, gas, supply)."""
|
| 634 |
icon_id = icon_id.strip().lower()
|
dev.sh
CHANGED
|
@@ -46,7 +46,7 @@ log "Démarrage backend → http://localhost:8000"
|
|
| 46 |
(
|
| 47 |
cd "$ROOT/backend"
|
| 48 |
set -a; source "$ROOT/.env"; set +a
|
| 49 |
-
uvicorn main:app --reload --host 0.0.0.0 --port 8000 2>&1 | sed 's/^/\033[0;33m[back]\033[0m /'
|
| 50 |
) &
|
| 51 |
BACKEND_PID=$!
|
| 52 |
|
|
|
|
| 46 |
(
|
| 47 |
cd "$ROOT/backend"
|
| 48 |
set -a; source "$ROOT/.env"; set +a
|
| 49 |
+
DEBUG=true uvicorn main:app --reload --host 0.0.0.0 --port 8000 2>&1 | sed 's/^/\033[0;33m[back]\033[0m /'
|
| 50 |
) &
|
| 51 |
BACKEND_PID=$!
|
| 52 |
|
frontend/.svelte-kit/types/route_meta_data.json
CHANGED
|
@@ -2,15 +2,23 @@
|
|
| 2 |
"/": [
|
| 3 |
"src/routes/+page.js"
|
| 4 |
],
|
| 5 |
-
"/admin": [
|
|
|
|
|
|
|
| 6 |
"/admin/compiled-map": [
|
| 7 |
-
"src/routes/admin/compiled-map/+page.ts"
|
|
|
|
| 8 |
],
|
| 9 |
"/admin/map": [
|
| 10 |
-
"src/routes/admin/map/+page.ts"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
],
|
| 12 |
-
"/admin/sounds": [],
|
| 13 |
-
"/admin/sprites": [],
|
| 14 |
"/game": [
|
| 15 |
"src/routes/game/+page.js"
|
| 16 |
],
|
|
|
|
| 2 |
"/": [
|
| 3 |
"src/routes/+page.js"
|
| 4 |
],
|
| 5 |
+
"/admin": [
|
| 6 |
+
"src/routes/admin/+layout.ts"
|
| 7 |
+
],
|
| 8 |
"/admin/compiled-map": [
|
| 9 |
+
"src/routes/admin/compiled-map/+page.ts",
|
| 10 |
+
"src/routes/admin/+layout.ts"
|
| 11 |
],
|
| 12 |
"/admin/map": [
|
| 13 |
+
"src/routes/admin/map/+page.ts",
|
| 14 |
+
"src/routes/admin/+layout.ts"
|
| 15 |
+
],
|
| 16 |
+
"/admin/sounds": [
|
| 17 |
+
"src/routes/admin/+layout.ts"
|
| 18 |
+
],
|
| 19 |
+
"/admin/sprites": [
|
| 20 |
+
"src/routes/admin/+layout.ts"
|
| 21 |
],
|
|
|
|
|
|
|
| 22 |
"/game": [
|
| 23 |
"src/routes/game/+page.js"
|
| 24 |
],
|
frontend/.svelte-kit/types/src/routes/admin/$types.d.ts
CHANGED
|
@@ -16,5 +16,7 @@ type LayoutParams = RouteParams & { }
|
|
| 16 |
type LayoutParentData = EnsureDefined<import('../$types.js').LayoutData>;
|
| 17 |
|
| 18 |
export type LayoutServerData = null;
|
| 19 |
-
export type
|
|
|
|
|
|
|
| 20 |
export type LayoutProps = { params: LayoutParams; data: LayoutData; children: import("svelte").Snippet }
|
|
|
|
| 16 |
type LayoutParentData = EnsureDefined<import('../$types.js').LayoutData>;
|
| 17 |
|
| 18 |
export type LayoutServerData = null;
|
| 19 |
+
export type LayoutLoad<OutputData extends OutputDataShape<LayoutParentData> = OutputDataShape<LayoutParentData>> = Kit.Load<LayoutParams, LayoutServerData, LayoutParentData, OutputData, LayoutRouteId>;
|
| 20 |
+
export type LayoutLoadEvent = Parameters<LayoutLoad>[0];
|
| 21 |
+
export type LayoutData = Expand<Omit<LayoutParentData, keyof Kit.LoadProperties<Awaited<ReturnType<typeof import('./proxy+layout.js').load>>>> & OptionalUnion<EnsureDefined<Kit.LoadProperties<Awaited<ReturnType<typeof import('./proxy+layout.js').load>>>>>>;
|
| 22 |
export type LayoutProps = { params: LayoutParams; data: LayoutData; children: import("svelte").Snippet }
|
frontend/.svelte-kit/types/src/routes/admin/proxy+layout.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// @ts-nocheck
|
| 2 |
+
import { redirect } from '@sveltejs/kit';
|
| 3 |
+
import type { LayoutLoad } from './$types';
|
| 4 |
+
|
| 5 |
+
export const ssr = false;
|
| 6 |
+
|
| 7 |
+
export const load = async ({ fetch }: Parameters<LayoutLoad>[0]) => {
|
| 8 |
+
const res = await fetch('/api/config');
|
| 9 |
+
if (!res.ok) throw redirect(302, '/');
|
| 10 |
+
const { debug } = await res.json();
|
| 11 |
+
if (!debug) throw redirect(302, '/');
|
| 12 |
+
};
|
frontend/src/routes/admin/+layout.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { redirect } from '@sveltejs/kit';
|
| 2 |
+
import type { LayoutLoad } from './$types';
|
| 3 |
+
|
| 4 |
+
export const ssr = false;
|
| 5 |
+
|
| 6 |
+
export const load: LayoutLoad = async ({ fetch }) => {
|
| 7 |
+
const res = await fetch('/api/config');
|
| 8 |
+
if (!res.ok) throw redirect(302, '/');
|
| 9 |
+
const { debug } = await res.json();
|
| 10 |
+
if (!debug) throw redirect(302, '/');
|
| 11 |
+
};
|