From 4c08b00832f89eb19cbc53f9217dd419a41ffb87 Mon Sep 17 00:00:00 2001 From: empty Date: Tue, 30 Dec 2025 16:50:07 +0800 Subject: [PATCH] feat: implement basic RPG mechanics with boss counter-attack MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add Player and Boss data models (models.py) - Implement command processing via regex matching (attack/heal/status) - Add boss counter-attack mechanism (15 dmg per player attack) - Add player death/respawn system (lose half gold, respawn full HP) - Update frontend with boss HP bar, player stats panel, quick action buttons - Add colored event log (red for attack, green for heal) - Change port from 8000 to 8080 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- backend/app/engine.py | 237 ++++++++++++++++++++++++++++++------- backend/app/main.py | 17 ++- backend/app/models.py | 66 +++++++++++ backend/app/schemas.py | 7 ++ frontend/app.js | 189 ++++++++++++++++++++++++++++- frontend/debug_client.html | 148 ++++++++++++++++++++++- run.py | 2 +- 7 files changed, 615 insertions(+), 51 deletions(-) create mode 100644 backend/app/models.py diff --git a/backend/app/engine.py b/backend/app/engine.py index 3694caf..3db97de 100644 --- a/backend/app/engine.py +++ b/backend/app/engine.py @@ -1,63 +1,76 @@ """ Core Game Engine - The "Heartbeat" of the game. -Runs the main game loop and coordinates between comments and agents. +Manages game state, processes commands, and coordinates broadcasts. """ import asyncio import logging -import random +import re import time from typing import TYPE_CHECKING from .schemas import GameEvent, EventType -from .agents import BaseAgent, RuleBasedAgent +from .models import Player, Boss if TYPE_CHECKING: from .server import ConnectionManager logger = logging.getLogger(__name__) +# Command patterns for string matching +ATTACK_PATTERN = re.compile(r"(attack|攻击|打|砍|杀)", re.IGNORECASE) +HEAL_PATTERN = re.compile(r"(heal|治疗|回血|加血|恢复)", re.IGNORECASE) +STATUS_PATTERN = re.compile(r"(status|查询|状态|信息)", re.IGNORECASE) + +# Game constants +ATTACK_DAMAGE = 10 +ATTACK_GOLD_REWARD = 10 +HEAL_AMOUNT = 10 +BOSS_COUNTER_DAMAGE = 15 # Boss反击伤害 + class GameEngine: """ - The core game engine that runs the main game loop. + The core game engine that manages RPG state and game loop. - Simulates live comments, processes them through agents, - and broadcasts responses to connected clients. + Manages players, boss, and processes commands through string matching. """ def __init__(self, connection_manager: "ConnectionManager") -> None: """ - Initialize the game engine. + Initialize the game engine with state storage. Args: connection_manager: The WebSocket connection manager for broadcasting """ self._manager = connection_manager - self._agent: BaseAgent = RuleBasedAgent(name="Guardian") self._running = False self._tick_count = 0 self._tick_interval = 2.0 # seconds - # Mock comment templates - self._mock_users = ["User123", "GamerPro", "DragonSlayer", "NightOwl", "StarGazer"] - self._mock_actions = ["Attack!", "Heal me!", "Run away!", "Help!", "Fire spell!", "Magic blast!"] + # Game state + self.players: dict[str, Player] = {} + self.boss = Boss(name="Dragon", hp=1000, max_hp=1000) @property def is_running(self) -> bool: """Check if the engine is currently running.""" return self._running - def _generate_mock_comment(self) -> tuple[str, str]: + def _get_or_create_player(self, username: str) -> Player: """ - Generate a mock live comment. + Get existing player or create new one. + + Args: + username: The player's username Returns: - Tuple of (username, comment_text) + Player instance """ - user = random.choice(self._mock_users) - action = random.choice(self._mock_actions) - return user, action + if username not in self.players: + self.players[username] = Player(name=username) + logger.info(f"New player registered: {username}") + return self.players[username] async def _broadcast_event(self, event_type: str, data: dict) -> None: """ @@ -74,57 +87,201 @@ class GameEngine: ) await self._manager.broadcast(event) + async def _broadcast_boss_update(self) -> None: + """Broadcast current boss status to all clients.""" + await self._broadcast_event( + EventType.BOSS_UPDATE, + { + "boss_name": self.boss.name, + "boss_hp": self.boss.hp, + "boss_max_hp": self.boss.max_hp, + "boss_hp_percentage": self.boss.hp_percentage + } + ) + + async def _handle_attack(self, player: Player) -> None: + """ + Handle attack command. Boss will counter-attack! + + Args: + player: The attacking player + """ + if not self.boss.is_alive: + await self._broadcast_event( + EventType.SYSTEM, + {"message": f"Boss is already defeated! Waiting for respawn..."} + ) + return + + # Player attacks boss + damage = self.boss.take_damage(ATTACK_DAMAGE) + player.add_gold(ATTACK_GOLD_REWARD) + + # Boss counter-attacks player + counter_damage = player.take_damage(BOSS_COUNTER_DAMAGE) + + await self._broadcast_event( + EventType.ATTACK, + { + "user": player.name, + "damage": damage, + "counter_damage": counter_damage, + "gold_earned": ATTACK_GOLD_REWARD, + "boss_hp": self.boss.hp, + "boss_max_hp": self.boss.max_hp, + "player_hp": player.hp, + "player_max_hp": player.max_hp, + "player_gold": player.gold, + "message": f"⚔️ {player.name} attacked {self.boss.name}! " + f"Dealt {damage} dmg, received {counter_damage} counter-attack. " + f"HP: {player.hp}/{player.max_hp} | Boss: {self.boss.hp}/{self.boss.max_hp}" + } + ) + + await self._broadcast_boss_update() + + # Check if player died + if not player.is_alive: + await self._handle_player_death(player) + return + + # Check if boss defeated + if not self.boss.is_alive: + await self._handle_boss_defeated() + + async def _handle_player_death(self, player: Player) -> None: + """Handle player death - respawn with full HP but lose half gold.""" + lost_gold = player.gold // 2 + player.gold -= lost_gold + player.hp = player.max_hp # Respawn with full HP + + await self._broadcast_event( + EventType.SYSTEM, + { + "user": player.name, + "player_hp": player.hp, + "player_max_hp": player.max_hp, + "player_gold": player.gold, + "message": f"💀 {player.name} was slain by {self.boss.name}! " + f"Lost {lost_gold} gold. Respawned with full HP." + } + ) + + async def _handle_boss_defeated(self) -> None: + """Handle boss defeat and reset.""" + await self._broadcast_event( + EventType.BOSS_DEFEATED, + { + "boss_name": self.boss.name, + "message": f"🎉 {self.boss.name} has been defeated! A new boss will spawn soon..." + } + ) + + # Reset boss after short delay + await asyncio.sleep(3.0) + self.boss.reset() + await self._broadcast_event( + EventType.SYSTEM, + {"message": f"⚔️ {self.boss.name} has respawned with full HP!"} + ) + await self._broadcast_boss_update() + + async def _handle_heal(self, player: Player) -> None: + """ + Handle heal command. + + Args: + player: The healing player + """ + healed = player.heal(HEAL_AMOUNT) + + await self._broadcast_event( + EventType.HEAL, + { + "user": player.name, + "healed": healed, + "player_hp": player.hp, + "player_max_hp": player.max_hp, + "message": f"{player.name} healed themselves. " + f"Restored {healed} HP. HP: {player.hp}/{player.max_hp}" + } + ) + + async def _handle_status(self, player: Player) -> None: + """ + Handle status query command. + + Args: + player: The querying player + """ + await self._broadcast_event( + EventType.STATUS, + { + "user": player.name, + "player_hp": player.hp, + "player_max_hp": player.max_hp, + "player_gold": player.gold, + "boss_name": self.boss.name, + "boss_hp": self.boss.hp, + "boss_max_hp": self.boss.max_hp, + "message": f"📊 {player.name}'s Status - HP: {player.hp}/{player.max_hp}, " + f"Gold: {player.gold} | Boss {self.boss.name}: {self.boss.hp}/{self.boss.max_hp}" + } + ) + async def process_comment(self, user: str, message: str) -> None: """ - Process a comment (real or mock) through the agent pipeline. + Process a comment through string matching command system. Args: user: Username of the commenter message: The comment text """ - # Broadcast the incoming comment + # Get or create player + player = self._get_or_create_player(user) + + # Broadcast the incoming comment first await self._broadcast_event( EventType.COMMENT, {"user": user, "message": message} ) - # Process through agent - full_comment = f"{user}: {message}" - response = self._agent.process_input(full_comment) - - # Broadcast agent response - await self._broadcast_event( - EventType.AGENT_RESPONSE, - {"agent": self._agent.name, "response": response} - ) + # Process command through string matching + if ATTACK_PATTERN.search(message): + await self._handle_attack(player) + elif HEAL_PATTERN.search(message): + await self._handle_heal(player) + elif STATUS_PATTERN.search(message): + await self._handle_status(player) + # If no command matched, treat as regular chat (no action needed) async def _game_loop(self) -> None: """ The main game loop - runs continuously while engine is active. - Every tick: - 1. Simulates a mock live comment - 2. Passes it to the agent - 3. Broadcasts the response + Every tick broadcasts current game state. """ logger.info("Game loop started") + # Broadcast initial boss state + await self._broadcast_boss_update() + while self._running: self._tick_count += 1 - # Broadcast tick event + # Broadcast tick event with game state await self._broadcast_event( EventType.TICK, - {"tick": self._tick_count} + { + "tick": self._tick_count, + "boss_hp": self.boss.hp, + "boss_max_hp": self.boss.max_hp, + "player_count": len(self.players) + } ) - # Generate and process mock comment - user, message = self._generate_mock_comment() - logger.info(f"Tick {self._tick_count}: {user} says '{message}'") + logger.debug(f"Tick {self._tick_count}: Boss HP {self.boss.hp}/{self.boss.max_hp}") - await self.process_comment(user, message) - - # Wait for next tick await asyncio.sleep(self._tick_interval) logger.info("Game loop stopped") diff --git a/backend/app/main.py b/backend/app/main.py index e0e24f4..496da24 100644 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -5,9 +5,12 @@ Configures the application, WebSocket routes, and lifecycle events. import logging from contextlib import asynccontextmanager +from pathlib import Path from fastapi import FastAPI, WebSocket, WebSocketDisconnect from fastapi.middleware.cors import CORSMiddleware +from fastapi.staticfiles import StaticFiles +from fastapi.responses import FileResponse from .server import ConnectionManager from .engine import GameEngine @@ -24,6 +27,9 @@ logger = logging.getLogger(__name__) manager = ConnectionManager() engine = GameEngine(manager) +# Frontend path +FRONTEND_DIR = Path(__file__).parent.parent.parent / "frontend" + @asynccontextmanager async def lifespan(app: FastAPI): @@ -55,9 +61,14 @@ app.add_middleware( allow_headers=["*"], ) - @app.get("/") async def root(): + """Serve the debug client page.""" + return FileResponse(FRONTEND_DIR / "debug_client.html") + + +@app.get("/health") +async def health(): """Health check endpoint.""" return { "status": "running", @@ -100,3 +111,7 @@ async def websocket_endpoint(websocket: WebSocket): except Exception as e: logger.error(f"WebSocket error: {e}") manager.disconnect(websocket) + + +# Mount static files (must be after all routes) +app.mount("/static", StaticFiles(directory=FRONTEND_DIR), name="static") diff --git a/backend/app/models.py b/backend/app/models.py new file mode 100644 index 0000000..1893943 --- /dev/null +++ b/backend/app/models.py @@ -0,0 +1,66 @@ +""" +Game entity models for The Island. +Defines Player and Boss data structures. +""" + +from pydantic import BaseModel, Field + + +class Player(BaseModel): + """ + Represents a player in the game world. + """ + name: str + hp: int = Field(default=100, ge=0) + max_hp: int = Field(default=100, gt=0) + gold: int = Field(default=0, ge=0) + + def take_damage(self, amount: int) -> int: + """Apply damage to player, returns actual damage dealt.""" + actual = min(amount, self.hp) + self.hp -= actual + return actual + + def heal(self, amount: int) -> int: + """Heal player, returns actual HP restored.""" + actual = min(amount, self.max_hp - self.hp) + self.hp += actual + return actual + + def add_gold(self, amount: int) -> None: + """Add gold to player.""" + self.gold += amount + + @property + def is_alive(self) -> bool: + """Check if player is alive.""" + return self.hp > 0 + + +class Boss(BaseModel): + """ + Represents a boss enemy in the game. + """ + name: str + hp: int = Field(ge=0) + max_hp: int = Field(gt=0) + + def take_damage(self, amount: int) -> int: + """Apply damage to boss, returns actual damage dealt.""" + actual = min(amount, self.hp) + self.hp -= actual + return actual + + def reset(self) -> None: + """Reset boss to full HP.""" + self.hp = self.max_hp + + @property + def is_alive(self) -> bool: + """Check if boss is alive.""" + return self.hp > 0 + + @property + def hp_percentage(self) -> float: + """Get HP as percentage.""" + return (self.hp / self.max_hp) * 100 if self.max_hp > 0 else 0 diff --git a/backend/app/schemas.py b/backend/app/schemas.py index ab6cd61..e1add3d 100644 --- a/backend/app/schemas.py +++ b/backend/app/schemas.py @@ -16,6 +16,13 @@ class EventType(str, Enum): TICK = "tick" SYSTEM = "system" ERROR = "error" + # RPG-specific events + ATTACK = "attack" + HEAL = "heal" + STATUS = "status" + BOSS_UPDATE = "boss_update" + BOSS_DEFEATED = "boss_defeated" + PLAYER_UPDATE = "player_update" class GameEvent(BaseModel): diff --git a/frontend/app.js b/frontend/app.js index ae4491c..8311be8 100644 --- a/frontend/app.js +++ b/frontend/app.js @@ -1,10 +1,17 @@ /** * The Island - Debug Client JavaScript - * Handles WebSocket connection and UI interactions + * Handles WebSocket connection, UI interactions, and game state display */ let ws = null; -const WS_URL = 'ws://localhost:8000/ws'; +const WS_URL = 'ws://localhost:8080/ws'; + +// Player state (tracked from server events) +let playerState = { + hp: 100, + maxHp: 100, + gold: 0 +}; // DOM Elements const statusDot = document.getElementById('statusDot'); @@ -14,6 +21,17 @@ const eventLog = document.getElementById('eventLog'); const usernameInput = document.getElementById('username'); const messageInput = document.getElementById('message'); const autoScrollCheckbox = document.getElementById('autoScroll'); +const hideTicksCheckbox = document.getElementById('hideTicks'); + +// Boss UI Elements +const bossName = document.getElementById('bossName'); +const bossHpText = document.getElementById('bossHpText'); +const bossHealthBar = document.getElementById('bossHealthBar'); +const bossHealthLabel = document.getElementById('bossHealthLabel'); + +// Player UI Elements +const playerHpDisplay = document.getElementById('playerHp'); +const playerGoldDisplay = document.getElementById('playerGold'); /** * Toggle WebSocket connection @@ -59,7 +77,7 @@ function connect() { ws.onmessage = (event) => { try { const data = JSON.parse(event.data); - logEvent(data); + handleGameEvent(data); } catch (e) { console.error('Failed to parse message:', e); } @@ -67,7 +85,132 @@ function connect() { } /** - * Send a mock comment to the server + * Handle incoming game events + */ +function handleGameEvent(event) { + const eventType = event.event_type; + const data = event.data || {}; + + // Update UI based on event type + switch (eventType) { + case 'boss_update': + updateBossUI(data); + break; + case 'attack': + updateBossFromAttack(data); + updatePlayerFromEvent(data); + break; + case 'heal': + updatePlayerFromEvent(data); + break; + case 'status': + updatePlayerFromEvent(data); + updateBossFromStatus(data); + break; + case 'system': + // Handle player death/respawn updates + if (data.user && data.player_hp !== undefined) { + updatePlayerFromEvent(data); + } + break; + case 'tick': + if (data.boss_hp !== undefined) { + updateBossUI({ + boss_hp: data.boss_hp, + boss_max_hp: data.boss_max_hp + }); + } + break; + } + + // Log the event + logEvent(event); +} + +/** + * Update Boss health bar UI + */ +function updateBossUI(data) { + if (data.boss_name) { + bossName.textContent = data.boss_name; + } + if (data.boss_hp !== undefined && data.boss_max_hp !== undefined) { + const hp = data.boss_hp; + const maxHp = data.boss_max_hp; + const percentage = maxHp > 0 ? (hp / maxHp) * 100 : 0; + + bossHpText.textContent = `HP: ${hp} / ${maxHp}`; + bossHealthBar.style.width = `${percentage}%`; + bossHealthLabel.textContent = `${Math.round(percentage)}%`; + + // Change color based on HP percentage + if (percentage <= 25) { + bossHealthBar.style.background = 'linear-gradient(90deg, #ff2222 0%, #ff4444 100%)'; + } else if (percentage <= 50) { + bossHealthBar.style.background = 'linear-gradient(90deg, #ff6600 0%, #ff8844 100%)'; + } else { + bossHealthBar.style.background = 'linear-gradient(90deg, #ff4444 0%, #ff6666 100%)'; + } + } +} + +/** + * Update boss from attack event + */ +function updateBossFromAttack(data) { + if (data.boss_hp !== undefined && data.boss_max_hp !== undefined) { + updateBossUI({ + boss_hp: data.boss_hp, + boss_max_hp: data.boss_max_hp + }); + } +} + +/** + * Update boss from status event + */ +function updateBossFromStatus(data) { + if (data.boss_hp !== undefined && data.boss_max_hp !== undefined) { + updateBossUI({ + boss_name: data.boss_name, + boss_hp: data.boss_hp, + boss_max_hp: data.boss_max_hp + }); + } +} + +/** + * Update player stats from event data + */ +function updatePlayerFromEvent(data) { + const currentUser = usernameInput.value.trim() || 'Anonymous'; + + // Only update if this event is for the current user + if (data.user !== currentUser) return; + + if (data.player_hp !== undefined) { + playerState.hp = data.player_hp; + } + if (data.player_max_hp !== undefined) { + playerState.maxHp = data.player_max_hp; + } + if (data.player_gold !== undefined) { + playerState.gold = data.player_gold; + } + + updatePlayerUI(); +} + +/** + * Update player stats UI + */ +function updatePlayerUI() { + playerHpDisplay.textContent = `${playerState.hp}/${playerState.maxHp}`; + playerGoldDisplay.textContent = playerState.gold; +} + +/** + * Send a comment/command to the server */ function sendComment() { if (!ws || ws.readyState !== WebSocket.OPEN) { @@ -92,6 +235,25 @@ function sendComment() { messageInput.value = ''; } +/** + * Quick action buttons + */ +function quickAction(action) { + if (!ws || ws.readyState !== WebSocket.OPEN) { + alert('Not connected to server'); + return; + } + + const user = usernameInput.value.trim() || 'Anonymous'; + + const payload = { + action: 'send_comment', + payload: { user, message: action } + }; + + ws.send(JSON.stringify(payload)); +} + /** * Format timestamp for display */ @@ -110,10 +272,17 @@ function formatEventData(eventType, data) { case 'agent_response': return data.response; case 'tick': - return `Tick #${data.tick}`; + return `Tick #${data.tick} | Players: ${data.player_count || 0}`; case 'system': case 'error': return data.message; + case 'attack': + case 'heal': + case 'status': + case 'boss_defeated': + return data.message; + case 'boss_update': + return `Boss ${data.boss_name}: ${data.boss_hp}/${data.boss_max_hp} (${Math.round(data.boss_hp_percentage)}%)`; default: return JSON.stringify(data); } @@ -127,6 +296,16 @@ function logEvent(event) { const timestamp = event.timestamp || Date.now() / 1000; const data = event.data || {}; + // Skip tick events if checkbox is checked + if (eventType === 'tick' && hideTicksCheckbox && hideTicksCheckbox.checked) { + return; + } + + // Skip boss_update events to reduce log noise (they're reflected in the UI) + if (eventType === 'boss_update') { + return; + } + const div = document.createElement('div'); div.className = `event ${eventType}`; div.innerHTML = ` diff --git a/frontend/debug_client.html b/frontend/debug_client.html index 7f4c209..7499f51 100644 --- a/frontend/debug_client.html +++ b/frontend/debug_client.html @@ -51,6 +51,88 @@ background: #44ff44; box-shadow: 0 0 10px rgba(68, 255, 68, 0.5); } + + /* Boss Health Bar */ + .boss-panel { + background: rgba(255, 68, 68, 0.1); + border: 1px solid rgba(255, 68, 68, 0.3); + border-radius: 12px; + padding: 20px; + margin-bottom: 20px; + } + .boss-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 10px; + } + .boss-name { + color: #ff6666; + font-size: 1.3rem; + font-weight: bold; + } + .boss-hp-text { + color: #ffaaaa; + font-size: 0.9rem; + } + .health-bar-container { + width: 100%; + height: 30px; + background: rgba(0, 0, 0, 0.4); + border-radius: 15px; + overflow: hidden; + position: relative; + } + .health-bar { + height: 100%; + background: linear-gradient(90deg, #ff4444 0%, #ff6666 100%); + border-radius: 15px; + transition: width 0.3s ease; + } + .health-bar-label { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + font-weight: bold; + text-shadow: 1px 1px 2px rgba(0,0,0,0.8); + } + + /* Player Stats Panel */ + .player-panel { + background: rgba(0, 212, 255, 0.1); + border: 1px solid rgba(0, 212, 255, 0.3); + border-radius: 12px; + padding: 20px; + margin-bottom: 20px; + } + .player-header { + color: #00d4ff; + font-size: 1.1rem; + margin-bottom: 15px; + } + .player-stats { + display: flex; + gap: 30px; + flex-wrap: wrap; + } + .stat-item { + display: flex; + align-items: center; + gap: 10px; + } + .stat-icon { + font-size: 1.5rem; + } + .stat-value { + font-size: 1.2rem; + font-weight: bold; + } + .stat-label { + font-size: 0.8rem; + color: #888; + } + .panels { display: flex; flex-direction: column; @@ -104,6 +186,19 @@ background: #666; cursor: not-allowed; } + .quick-actions { + display: flex; + gap: 10px; + margin-top: 10px; + flex-wrap: wrap; + } + .quick-btn { + padding: 8px 16px; + font-size: 13px; + } + .quick-btn.attack { background: #ff6666; } + .quick-btn.heal { background: #44ff88; } + .quick-btn.status { background: #ffaa44; } .controls { display: flex; gap: 15px; @@ -117,7 +212,7 @@ font-size: 14px; } .event-log { - height: 400px; + height: 350px; overflow-y: auto; background: rgba(0, 0, 0, 0.4); border-radius: 8px; @@ -133,9 +228,15 @@ } .event.comment { border-color: #ffaa00; background: rgba(255, 170, 0, 0.1); } .event.agent_response { border-color: #00ff88; background: rgba(0, 255, 136, 0.1); } - .event.tick { border-color: #888; background: rgba(136, 136, 136, 0.1); } + .event.tick { border-color: #888; background: rgba(136, 136, 136, 0.05); opacity: 0.6; } .event.system { border-color: #00d4ff; background: rgba(0, 212, 255, 0.1); } .event.error { border-color: #ff4444; background: rgba(255, 68, 68, 0.1); } + /* RPG Event Styles */ + .event.attack { border-color: #ff4444; background: rgba(255, 68, 68, 0.15); color: #ff8888; } + .event.heal { border-color: #44ff88; background: rgba(68, 255, 136, 0.15); color: #88ffaa; } + .event.status { border-color: #ffaa44; background: rgba(255, 170, 68, 0.1); } + .event.boss_update { border-color: #ff6666; background: rgba(255, 102, 102, 0.1); } + .event.boss_defeated { border-color: #ffdd00; background: rgba(255, 221, 0, 0.2); color: #ffee88; font-weight: bold; } .event-time { color: #888; font-size: 11px; } .event-type { font-weight: bold; text-transform: uppercase; font-size: 11px; } .event-data { margin-top: 5px; } @@ -153,14 +254,52 @@ + +
+
+ Dragon + HP: 1000 / 1000 +
+
+
+ 100% +
+
+ + +
+
Your Stats
+
+
+ ❤️ +
+
100/100
+
HP
+
+
+
+ 💰 +
+
0
+
Gold
+
+
+
+
+
-

Send Mock Comment

+

Send Command

- +
+
+ + + +
@@ -168,6 +307,7 @@
+
diff --git a/run.py b/run.py index f1c1895..5c3513b 100644 --- a/run.py +++ b/run.py @@ -10,7 +10,7 @@ if __name__ == "__main__": uvicorn.run( "backend.app.main:app", host="0.0.0.0", - port=8000, + port=8080, reload=True, log_level="info" )