|
import asyncio
|
|
from contextlib import asynccontextmanager
|
|
from fastapi import FastAPI, WebSocket, WebSocketDisconnect
|
|
from fastapi.staticfiles import StaticFiles
|
|
from fastapi.responses import FileResponse
|
|
from pathlib import Path
|
|
|
|
from server.websocket_manager import WebSocketManager
|
|
from server.game_state import GameState
|
|
from server.economy import EconomyEngine
|
|
from server.database import Database
|
|
|
|
# Global instances
|
|
ws_manager = WebSocketManager()
|
|
game_state = GameState()
|
|
economy_engine = EconomyEngine(game_state)
|
|
database = Database()
|
|
|
|
# Background task for economy ticks and persistence
|
|
async def game_loop():
|
|
"""Main game loop: economy ticks every 10 seconds, DB save every 10 seconds"""
|
|
while True:
|
|
await asyncio.sleep(10)
|
|
|
|
# Economy tick
|
|
economy_engine.tick()
|
|
|
|
# Save to database
|
|
database.save_game_state(game_state)
|
|
|
|
# Broadcast state to all players
|
|
await ws_manager.broadcast_game_state(game_state.get_state())
|
|
|
|
@asynccontextmanager
|
|
async def lifespan(app: FastAPI):
|
|
"""Startup and shutdown events"""
|
|
# Startup
|
|
database.init_db()
|
|
game_state.load_state(database.load_game_state())
|
|
|
|
# Start game loop
|
|
task = asyncio.create_task(game_loop())
|
|
|
|
yield
|
|
|
|
# Shutdown
|
|
task.cancel()
|
|
database.save_game_state(game_state)
|
|
|
|
app = FastAPI(lifespan=lifespan)
|
|
|
|
# Mount static files
|
|
app.mount("/static", StaticFiles(directory="static"), name="static")
|
|
|
|
@app.get("/")
|
|
async def root():
|
|
"""Serve index.html"""
|
|
return FileResponse("static/index.html")
|
|
|
|
@app.websocket("/ws/{nickname}")
|
|
async def websocket_endpoint(websocket: WebSocket, nickname: str):
|
|
"""WebSocket endpoint for real-time game communication"""
|
|
await ws_manager.connect(websocket, nickname)
|
|
|
|
try:
|
|
# Send initial game state
|
|
player_id = await ws_manager.get_player_id(websocket)
|
|
player = game_state.get_or_create_player(nickname, player_id)
|
|
|
|
await websocket.send_json({
|
|
"type": "init",
|
|
"player": player.to_dict(),
|
|
"game_state": game_state.get_state()
|
|
})
|
|
|
|
# Listen for messages
|
|
while True:
|
|
data = await websocket.receive_json()
|
|
await handle_message(websocket, data)
|
|
|
|
except WebSocketDisconnect:
|
|
await ws_manager.disconnect(websocket)
|
|
|
|
async def handle_message(websocket: WebSocket, data: dict):
|
|
"""Handle incoming WebSocket messages"""
|
|
msg_type = data.get("type")
|
|
player_id = await ws_manager.get_player_id(websocket)
|
|
|
|
if msg_type == "cursor_move":
|
|
# Broadcast cursor position
|
|
await ws_manager.broadcast({
|
|
"type": "cursor_move",
|
|
"player_id": player_id,
|
|
"x": data["x"],
|
|
"y": data["y"]
|
|
}, exclude=websocket)
|
|
|
|
elif msg_type == "place_building":
|
|
# Place building
|
|
result = game_state.place_building(
|
|
player_id,
|
|
data["building_type"],
|
|
data["x"],
|
|
data["y"]
|
|
)
|
|
|
|
if result["success"]:
|
|
# Broadcast to all players
|
|
await ws_manager.broadcast({
|
|
"type": "building_placed",
|
|
"building": result["building"]
|
|
})
|
|
else:
|
|
# Send error to player
|
|
await websocket.send_json({
|
|
"type": "error",
|
|
"message": result["error"]
|
|
})
|
|
|
|
elif msg_type == "remove_building":
|
|
# Remove building
|
|
result = game_state.remove_building(player_id, data["x"], data["y"])
|
|
|
|
if result["success"]:
|
|
await ws_manager.broadcast({
|
|
"type": "building_removed",
|
|
"x": data["x"],
|
|
"y": data["y"]
|
|
})
|
|
|
|
elif msg_type == "edit_building":
|
|
# Edit building name
|
|
result = game_state.edit_building_name(
|
|
player_id,
|
|
data["x"],
|
|
data["y"],
|
|
data["name"]
|
|
)
|
|
|
|
if result["success"]:
|
|
await ws_manager.broadcast({
|
|
"type": "building_updated",
|
|
"x": data["x"],
|
|
"y": data["y"],
|
|
"name": data["name"]
|
|
})
|
|
|
|
elif msg_type == "chat":
|
|
# Broadcast chat message
|
|
nickname = await ws_manager.get_nickname(websocket)
|
|
await ws_manager.broadcast({
|
|
"type": "chat",
|
|
"nickname": nickname,
|
|
"message": data["message"],
|
|
"timestamp": data["timestamp"]
|
|
})
|