157 lines
4.5 KiB
Python
Raw Normal View History

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"]
})