got some webserver

This commit is contained in:
2026-01-22 22:33:46 -07:00
parent 28696b2358
commit 6e082c24df
5 changed files with 91 additions and 200 deletions

View File

@@ -14,11 +14,6 @@ tokio-tungstenite = "0.24"
futures-util = "0.3" futures-util = "0.3"
tower-http = { version = "0.6", features = ["fs", "cors"] } tower-http = { version = "0.6", features = ["fs", "cors"] }
rand = "0.8" rand = "0.8"
leptos = { version = "0.6", features = ["csr"] }
leptos_axum = { version = "0.6" }
leptos_meta = { version = "0.6" }
leptos_router = { version = "0.6" }
protoss-bot-web = { path = "web" }
[profile.release] [profile.release]
opt-level = 3 opt-level = 3

View File

@@ -38,10 +38,11 @@ impl AiModule for ProtosBot {
return; return;
}; };
// Apply desired game speed // Apply desired game speed from shared state
let desired_speed = self.shared_speed.get();
unsafe { unsafe {
let game_ptr = game as *const Game as *mut Game; let game_ptr = game as *const Game as *mut Game;
(*game_ptr).set_local_speed(locked_state.desired_game_speed); (*game_ptr).set_local_speed(desired_speed);
} }
build_manager::on_frame(game, &player, &mut locked_state); build_manager::on_frame(game, &player, &mut locked_state);
@@ -90,10 +91,11 @@ impl AiModule for ProtosBot {
pub struct ProtosBot { pub struct ProtosBot {
game_state: Arc<Mutex<GameState>>, game_state: Arc<Mutex<GameState>>,
shared_speed: crate::web_server::SharedGameSpeed,
} }
impl ProtosBot { impl ProtosBot {
pub fn new(game_state: Arc<Mutex<GameState>>) -> Self { pub fn new(game_state: Arc<Mutex<GameState>>, shared_speed: crate::web_server::SharedGameSpeed) -> Self {
Self { game_state } Self { game_state, shared_speed }
} }
} }

View File

@@ -6,18 +6,20 @@ mod web_server;
use bot::ProtosBot; use bot::ProtosBot;
use state::game_state::GameState; use state::game_state::GameState;
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
use web_server::SharedGameSpeed;
fn main() { fn main() {
println!("Starting RustBot..."); println!("Starting RustBot...");
let game_state = Arc::new(Mutex::new(GameState::default())); let game_state = Arc::new(Mutex::new(GameState::default()));
let shared_speed = SharedGameSpeed::new(42); // Default speed (slowest)
// Start web server in a separate thread // Start web server in a separate thread
let game_state_clone = game_state.clone(); let shared_speed_clone = shared_speed.clone();
std::thread::spawn(move || { std::thread::spawn(move || {
let runtime = tokio::runtime::Runtime::new().unwrap(); let runtime = tokio::runtime::Runtime::new().unwrap();
runtime.block_on(web_server::start_web_server(game_state_clone)); runtime.block_on(web_server::start_web_server(shared_speed_clone));
}); });
rsbwapi::start(move |_game| ProtosBot::new(game_state.clone())); rsbwapi::start(move |_game| ProtosBot::new(game_state.clone(), shared_speed.clone()));
} }

View File

@@ -1,8 +1,7 @@
use crate::state::game_state::GameState;
use axum::{ use axum::{
extract::State, extract::State,
http::StatusCode, http::StatusCode,
response::IntoResponse, response::{IntoResponse, Response},
routing::{get, post}, routing::{get, post},
Json, Router, Json, Router,
}; };
@@ -11,8 +10,24 @@ use std::sync::{Arc, Mutex};
use tower_http::{cors::CorsLayer, services::ServeDir}; use tower_http::{cors::CorsLayer, services::ServeDir};
#[derive(Clone)] #[derive(Clone)]
pub struct AppState { pub struct SharedGameSpeed {
pub game_state: Arc<Mutex<GameState>>, speed: Arc<Mutex<i32>>,
}
impl SharedGameSpeed {
pub fn new(initial_speed: i32) -> Self {
Self {
speed: Arc::new(Mutex::new(initial_speed)),
}
}
pub fn get(&self) -> i32 {
*self.speed.lock().unwrap()
}
pub fn set(&self, speed: i32) {
*self.speed.lock().unwrap() = speed;
}
} }
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
@@ -25,69 +40,51 @@ pub struct GameSpeedRequest {
pub speed: i32, pub speed: i32,
} }
// GET /api/speed - Get current game speed async fn get_game_speed(State(state): State<SharedGameSpeed>) -> Response {
async fn get_game_speed(State(state): State<AppState>) -> impl IntoResponse { let speed = state.get();
let game_state = match state.game_state.lock() { (StatusCode::OK, Json(GameSpeedResponse { speed })).into_response()
Ok(gs) => gs,
Err(_) => return (StatusCode::INTERNAL_SERVER_ERROR, Json(GameSpeedResponse { speed: 0 })),
};
(
StatusCode::OK,
Json(GameSpeedResponse {
speed: game_state.desired_game_speed,
}),
)
} }
// POST /api/speed - Set game speed
async fn set_game_speed( async fn set_game_speed(
State(state): State<AppState>, State(state): State<SharedGameSpeed>,
Json(payload): Json<GameSpeedRequest>, Json(payload): Json<GameSpeedRequest>,
) -> impl IntoResponse { ) -> Response {
// Validate speed is within reasonable range (0-1000) if payload.speed < -1 || payload.speed > 1000 {
if payload.speed < 0 || payload.speed > 1000 {
return ( return (
StatusCode::BAD_REQUEST, StatusCode::BAD_REQUEST,
Json(GameSpeedResponse { speed: 0 }), Json(GameSpeedResponse { speed: 0 }),
); )
.into_response();
} }
let mut game_state = match state.game_state.lock() { state.set(payload.speed);
Ok(gs) => gs,
Err(_) => return (StatusCode::INTERNAL_SERVER_ERROR, Json(GameSpeedResponse { speed: 0 })),
};
game_state.desired_game_speed = payload.speed;
( (
StatusCode::OK, StatusCode::OK,
Json(GameSpeedResponse { Json(GameSpeedResponse {
speed: game_state.desired_game_speed, speed: payload.speed,
}), }),
) )
.into_response()
} }
pub async fn start_web_server(game_state: Arc<Mutex<GameState>>) { pub async fn start_web_server(shared_speed: SharedGameSpeed) {
let app_state = AppState { game_state }; let static_dir = std::env::current_dir().unwrap().join("static");
// Serve static files from the "static" directory let cors = CorsLayer::very_permissive();
let static_dir = std::env::current_dir()
.unwrap()
.join("static");
let app = Router::new() let app = Router::new()
.route("/api/speed", get(get_game_speed)) .route("/api/speed", get(get_game_speed))
.route("/api/speed", post(set_game_speed)) .route("/api/speed", post(set_game_speed))
.layer(cors)
.fallback_service(ServeDir::new(static_dir)) .fallback_service(ServeDir::new(static_dir))
.layer(CorsLayer::permissive()) .with_state(shared_speed);
.with_state(app_state);
let listener = tokio::net::TcpListener::bind("127.0.0.1:3000") let addr = "127.0.0.1:3333";
.await
.unwrap();
println!("Web server running at http://127.0.0.1:3000"); let listener = tokio::net::TcpListener::bind(addr).await.unwrap();
println!("Web server running at http://{}", addr);
axum::serve(listener, app).await.unwrap(); axum::serve(listener, app).await.unwrap();
} }

View File

@@ -13,7 +13,7 @@
body { body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); background: linear-gradient(135deg, #1a1a1a 0%, #2d2d2d 100%);
min-height: 100vh; min-height: 100vh;
display: flex; display: flex;
justify-content: center; justify-content: center;
@@ -22,35 +22,29 @@
} }
.container { .container {
background: white; background: #1e1e1e;
border: 2px solid #d4af37;
border-radius: 20px; border-radius: 20px;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3); box-shadow: 0 20px 60px rgba(212, 175, 55, 0.3);
padding: 40px; padding: 40px;
max-width: 500px; max-width: 500px;
width: 100%; width: 100%;
} }
h1 { h1 {
color: #333; color: #d4af37;
margin-bottom: 10px; margin-bottom: 30px;
font-size: 28px; font-size: 28px;
text-align: center; text-align: center;
} }
.subtitle {
color: #666;
text-align: center;
margin-bottom: 30px;
font-size: 14px;
}
.control-group { .control-group {
margin-bottom: 30px; margin-bottom: 30px;
} }
label { label {
display: block; display: block;
color: #555; color: #d4af37;
font-weight: 600; font-weight: 600;
margin-bottom: 10px; margin-bottom: 10px;
font-size: 14px; font-size: 14px;
@@ -64,83 +58,49 @@
.speed-value { .speed-value {
font-size: 48px; font-size: 48px;
font-weight: bold; font-weight: bold;
color: #667eea; color: #d4af37;
display: block; display: block;
} }
.speed-label { .speed-label {
color: #888; color: #999;
font-size: 12px; font-size: 12px;
text-transform: uppercase; text-transform: uppercase;
letter-spacing: 1px; letter-spacing: 1px;
} }
input[type="range"] {
width: 100%;
height: 8px;
border-radius: 5px;
background: #ddd;
outline: none;
-webkit-appearance: none;
margin-bottom: 20px;
}
input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 24px;
height: 24px;
border-radius: 50%;
background: #667eea;
cursor: pointer;
box-shadow: 0 2px 6px rgba(102, 126, 234, 0.4);
transition: all 0.2s ease;
}
input[type="range"]::-webkit-slider-thumb:hover {
transform: scale(1.1);
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.6);
}
input[type="range"]::-moz-range-thumb {
width: 24px;
height: 24px;
border-radius: 50%;
background: #667eea;
cursor: pointer;
border: none;
box-shadow: 0 2px 6px rgba(102, 126, 234, 0.4);
transition: all 0.2s ease;
}
input[type="range"]::-moz-range-thumb:hover {
transform: scale(1.1);
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.6);
}
.preset-buttons { .preset-buttons {
display: grid; display: grid;
grid-template-columns: repeat(3, 1fr); grid-template-columns: repeat(4, 1fr);
gap: 10px; gap: 10px;
margin-bottom: 20px; margin-bottom: 20px;
} }
button { button {
padding: 12px 20px; padding: 20px 12px;
border: none; border: 2px solid #d4af37;
border-radius: 8px; border-radius: 8px;
font-size: 14px; font-size: 18px;
font-weight: 600; font-weight: 600;
cursor: pointer; cursor: pointer;
transition: all 0.2s ease; transition: all 0.2s ease;
background: #f0f0f0; background: #2d2d2d;
color: #333; color: #d4af37;
}
button small {
display: block;
font-size: 11px;
font-weight: normal;
margin-top: 4px;
opacity: 0.7;
} }
button:hover { button:hover {
background: #e0e0e0; background: #d4af37;
color: #1e1e1e;
transform: translateY(-2px); transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); box-shadow: 0 4px 12px rgba(212, 175, 55, 0.4);
} }
button:active { button:active {
@@ -162,102 +122,51 @@
} }
.status.success { .status.success {
background: #d4edda; background: rgba(34, 197, 94, 0.2);
color: #155724; color: #4ade80;
border: 1px solid #c3e6cb; border: 1px solid #22c55e;
} }
.status.error { .status.error {
background: #f8d7da; background: rgba(239, 68, 68, 0.2);
color: #721c24; color: #f87171;
border: 1px solid #f5c6cb; border: 1px solid #ef4444;
}
.range-labels {
display: flex;
justify-content: space-between;
font-size: 12px;
color: #888;
margin-top: -15px;
margin-bottom: 20px;
}
.info-box {
background: #f8f9fa;
border-left: 4px solid #667eea;
padding: 15px;
border-radius: 4px;
margin-top: 20px;
font-size: 13px;
color: #555;
line-height: 1.6;
}
.info-box strong {
color: #333;
} }
</style> </style>
</head> </head>
<body> <body>
<div class="container"> <div class="container">
<h1>⚡ Game Speed Control</h1> <h1>⚡ Game Speed Control</h1>
<p class="subtitle">Protoss Bot Configuration</p>
<div class="speed-display"> <div class="speed-display">
<span class="speed-value" id="currentSpeed">20</span> <span class="speed-value" id="currentSpeed">42</span>
<span class="speed-label">Current Speed</span> <span class="speed-label">Current Speed</span>
</div> </div>
<div class="control-group"> <div class="control-group">
<label for="speedSlider">Adjust Speed</label> <label>Select Speed</label>
<input
type="range"
id="speedSlider"
min="0"
max="100"
value="20"
step="1"
>
<div class="range-labels">
<span>0 (Paused)</span>
<span>50 (Normal)</span>
<span>100 (Fast)</span>
</div>
</div>
<div class="preset-buttons"> <div class="preset-buttons">
<button onclick="setSpeed(0)">⏸ Pause</button> <button onclick="setSpeed(42)">42<br><small>Slowest</small></button>
<button onclick="setSpeed(20)">🐌 Slow</button> <button onclick="setSpeed(1)">1<br><small>Fast</small></button>
<button onclick="setSpeed(42)">▶️ Normal</button> <button onclick="setSpeed(0)">0<br><small>Fastest</small></button>
<button onclick="setSpeed(80)">⚡ Fast</button> <button onclick="setSpeed(-1)">-1<br><small>Max</small></button>
<button onclick="setSpeed(100)">🚀 Max</button> </div>
<button onclick="setSpeed(1000)">💨 Turbo</button>
</div> </div>
<div id="status" class="status"></div> <div id="status" class="status"></div>
<div class="info-box">
<strong>Speed Guide:</strong><br>
• 0 = Paused<br>
• 20 = Slow (good for debugging)<br>
• 42 = Normal game speed<br>
• 100+ = Fast forward
</div>
</div> </div>
<script> <script>
const speedSlider = document.getElementById('speedSlider');
const currentSpeedDisplay = document.getElementById('currentSpeed'); const currentSpeedDisplay = document.getElementById('currentSpeed');
const statusDiv = document.getElementById('status'); const statusDiv = document.getElementById('status');
// Fetch current speed on load // Fetch current speed on load
async function fetchCurrentSpeed() { async function fetchCurrentSpeed() {
try { try {
const response = await fetch('http://127.0.0.1:3000/api/speed'); const response = await fetch('http://127.0.0.1:3333/api/speed');
if (response.ok) { if (response.ok) {
const data = await response.json(); const data = await response.json();
updateSpeedDisplay(data.speed); updateSpeedDisplay(data.speed);
speedSlider.value = Math.min(data.speed, 100); // Cap slider at 100
} }
} catch (error) { } catch (error) {
showStatus('Unable to connect to bot', 'error'); showStatus('Unable to connect to bot', 'error');
@@ -267,15 +176,12 @@
// Update speed display // Update speed display
function updateSpeedDisplay(speed) { function updateSpeedDisplay(speed) {
currentSpeedDisplay.textContent = speed; currentSpeedDisplay.textContent = speed;
if (speed <= 100) {
speedSlider.value = speed;
}
} }
// Set speed via API // Set speed via API
async function setSpeed(speed) { async function setSpeed(speed) {
try { try {
const response = await fetch('http://127.0.0.1:3000/api/speed', { const response = await fetch('http://127.0.0.1:3333/api/speed', {
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
@@ -304,17 +210,6 @@
}, 3000); }, 3000);
} }
// Slider change handler
speedSlider.addEventListener('input', (e) => {
const speed = parseInt(e.target.value);
currentSpeedDisplay.textContent = speed;
});
speedSlider.addEventListener('change', (e) => {
const speed = parseInt(e.target.value);
setSpeed(speed);
});
// Fetch current speed on page load // Fetch current speed on page load
fetchCurrentSpeed(); fetchCurrentSpeed();