diff --git a/protossbot/Cargo.toml b/protossbot/Cargo.toml index ccb3be9..8914766 100644 --- a/protossbot/Cargo.toml +++ b/protossbot/Cargo.toml @@ -14,11 +14,6 @@ tokio-tungstenite = "0.24" futures-util = "0.3" tower-http = { version = "0.6", features = ["fs", "cors"] } 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] opt-level = 3 diff --git a/protossbot/src/bot.rs b/protossbot/src/bot.rs index bbb4c8c..ea002d8 100644 --- a/protossbot/src/bot.rs +++ b/protossbot/src/bot.rs @@ -38,10 +38,11 @@ impl AiModule for ProtosBot { return; }; - // Apply desired game speed + // Apply desired game speed from shared state + let desired_speed = self.shared_speed.get(); unsafe { 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); @@ -90,10 +91,11 @@ impl AiModule for ProtosBot { pub struct ProtosBot { game_state: Arc>, + shared_speed: crate::web_server::SharedGameSpeed, } impl ProtosBot { - pub fn new(game_state: Arc>) -> Self { - Self { game_state } + pub fn new(game_state: Arc>, shared_speed: crate::web_server::SharedGameSpeed) -> Self { + Self { game_state, shared_speed } } } diff --git a/protossbot/src/main.rs b/protossbot/src/main.rs index d040897..381a8d8 100644 --- a/protossbot/src/main.rs +++ b/protossbot/src/main.rs @@ -6,18 +6,20 @@ mod web_server; use bot::ProtosBot; use state::game_state::GameState; use std::sync::{Arc, Mutex}; +use web_server::SharedGameSpeed; fn main() { println!("Starting RustBot..."); 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 - let game_state_clone = game_state.clone(); + let shared_speed_clone = shared_speed.clone(); std::thread::spawn(move || { 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())); } diff --git a/protossbot/src/web_server.rs b/protossbot/src/web_server.rs index f641385..1b521f4 100644 --- a/protossbot/src/web_server.rs +++ b/protossbot/src/web_server.rs @@ -1,8 +1,7 @@ -use crate::state::game_state::GameState; use axum::{ extract::State, http::StatusCode, - response::IntoResponse, + response::{IntoResponse, Response}, routing::{get, post}, Json, Router, }; @@ -11,8 +10,24 @@ use std::sync::{Arc, Mutex}; use tower_http::{cors::CorsLayer, services::ServeDir}; #[derive(Clone)] -pub struct AppState { - pub game_state: Arc>, +pub struct SharedGameSpeed { + speed: Arc>, +} + +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)] @@ -25,69 +40,51 @@ pub struct GameSpeedRequest { pub speed: i32, } -// GET /api/speed - Get current game speed -async fn get_game_speed(State(state): State) -> impl IntoResponse { - let game_state = match state.game_state.lock() { - Ok(gs) => gs, - Err(_) => return (StatusCode::INTERNAL_SERVER_ERROR, Json(GameSpeedResponse { speed: 0 })), - }; - - ( - StatusCode::OK, - Json(GameSpeedResponse { - speed: game_state.desired_game_speed, - }), - ) +async fn get_game_speed(State(state): State) -> Response { + let speed = state.get(); + (StatusCode::OK, Json(GameSpeedResponse { speed })).into_response() } -// POST /api/speed - Set game speed async fn set_game_speed( - State(state): State, + State(state): State, Json(payload): Json, -) -> impl IntoResponse { - // Validate speed is within reasonable range (0-1000) - if payload.speed < 0 || payload.speed > 1000 { +) -> Response { + if payload.speed < -1 || payload.speed > 1000 { return ( StatusCode::BAD_REQUEST, Json(GameSpeedResponse { speed: 0 }), - ); + ) + .into_response(); } - let mut game_state = match state.game_state.lock() { - Ok(gs) => gs, - Err(_) => return (StatusCode::INTERNAL_SERVER_ERROR, Json(GameSpeedResponse { speed: 0 })), - }; - - game_state.desired_game_speed = payload.speed; + state.set(payload.speed); ( StatusCode::OK, Json(GameSpeedResponse { - speed: game_state.desired_game_speed, + speed: payload.speed, }), ) + .into_response() } -pub async fn start_web_server(game_state: Arc>) { - let app_state = AppState { game_state }; +pub async fn start_web_server(shared_speed: SharedGameSpeed) { + let static_dir = std::env::current_dir().unwrap().join("static"); - // Serve static files from the "static" directory - let static_dir = std::env::current_dir() - .unwrap() - .join("static"); + let cors = CorsLayer::very_permissive(); let app = Router::new() .route("/api/speed", get(get_game_speed)) .route("/api/speed", post(set_game_speed)) + .layer(cors) .fallback_service(ServeDir::new(static_dir)) - .layer(CorsLayer::permissive()) - .with_state(app_state); + .with_state(shared_speed); - let listener = tokio::net::TcpListener::bind("127.0.0.1:3000") - .await - .unwrap(); + let addr = "127.0.0.1:3333"; - 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(); } diff --git a/protossbot/static/index.html b/protossbot/static/index.html index dca5223..65638e5 100644 --- a/protossbot/static/index.html +++ b/protossbot/static/index.html @@ -13,7 +13,7 @@ body { 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; display: flex; justify-content: center; @@ -22,35 +22,29 @@ } .container { - background: white; + background: #1e1e1e; + border: 2px solid #d4af37; 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; max-width: 500px; width: 100%; } h1 { - color: #333; - margin-bottom: 10px; + color: #d4af37; + margin-bottom: 30px; font-size: 28px; text-align: center; } - .subtitle { - color: #666; - text-align: center; - margin-bottom: 30px; - font-size: 14px; - } - .control-group { margin-bottom: 30px; } label { display: block; - color: #555; + color: #d4af37; font-weight: 600; margin-bottom: 10px; font-size: 14px; @@ -64,83 +58,49 @@ .speed-value { font-size: 48px; font-weight: bold; - color: #667eea; + color: #d4af37; display: block; } .speed-label { - color: #888; + color: #999; font-size: 12px; text-transform: uppercase; 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 { display: grid; - grid-template-columns: repeat(3, 1fr); + grid-template-columns: repeat(4, 1fr); gap: 10px; margin-bottom: 20px; } button { - padding: 12px 20px; - border: none; + padding: 20px 12px; + border: 2px solid #d4af37; border-radius: 8px; - font-size: 14px; + font-size: 18px; font-weight: 600; cursor: pointer; transition: all 0.2s ease; - background: #f0f0f0; - color: #333; + background: #2d2d2d; + color: #d4af37; + } + + button small { + display: block; + font-size: 11px; + font-weight: normal; + margin-top: 4px; + opacity: 0.7; } button:hover { - background: #e0e0e0; + background: #d4af37; + color: #1e1e1e; 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 { @@ -162,102 +122,51 @@ } .status.success { - background: #d4edda; - color: #155724; - border: 1px solid #c3e6cb; + background: rgba(34, 197, 94, 0.2); + color: #4ade80; + border: 1px solid #22c55e; } .status.error { - background: #f8d7da; - color: #721c24; - border: 1px solid #f5c6cb; - } - - .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; + background: rgba(239, 68, 68, 0.2); + color: #f87171; + border: 1px solid #ef4444; }

⚡ Game Speed Control

-

Protoss Bot Configuration

- 20 + 42 Current Speed
- - -
- 0 (Paused) - 50 (Normal) - 100 (Fast) + +
+ + + +
-
- - - - - - -
-
- -
- Speed Guide:
- • 0 = Paused
- • 20 = Slow (good for debugging)
- • 42 = Normal game speed
- • 100+ = Fast forward -