#[cfg(feature = "wasm")]
use wasm_bindgen::prelude::*;
#[cfg(feature = "wasm")]
use crate::env::{
Environment, cartpole::CartPole, pong::Pong, simple_bandit::SimpleBandit, snake::SnakeEnv,
};
#[cfg(feature = "wasm")]
#[wasm_bindgen]
pub struct WasmCartPole {
env: CartPole,
episode: u32,
episode_steps: u32,
best_score: u32,
policy: Option<crate::policy::inference::InferenceModel>,
}
#[cfg(feature = "wasm")]
#[wasm_bindgen]
impl WasmCartPole {
#[wasm_bindgen(constructor)]
pub fn new() -> Self {
console_error_panic_hook::set_once();
Self { env: CartPole::new(), episode: 0, episode_steps: 0, best_score: 0, policy: None }
}
#[wasm_bindgen]
pub fn reset(&mut self) -> Vec<f32> {
if self.episode_steps > self.best_score {
self.best_score = self.episode_steps;
}
self.env.reset();
self.episode += 1;
self.episode_steps = 0;
self.env.get_state().to_vec()
}
#[wasm_bindgen]
pub fn step(&mut self, action: i32) -> Vec<f32> {
let result = self.env.step(action as i64);
self.episode_steps += 1;
let mut output = result.observation.clone();
output.push(result.reward);
output.push(if result.terminated { 1.0 } else { 0.0 });
output.push(if result.truncated { 1.0 } else { 0.0 });
if result.terminated || result.truncated {
if self.episode_steps > self.best_score {
self.best_score = self.episode_steps;
}
}
output
}
#[wasm_bindgen]
pub fn get_episode(&self) -> u32 {
self.episode
}
#[wasm_bindgen]
pub fn get_steps(&self) -> u32 {
self.episode_steps
}
#[wasm_bindgen]
pub fn get_best_score(&self) -> u32 {
self.best_score
}
#[wasm_bindgen]
pub fn get_state(&self) -> Vec<f32> {
self.env.get_state().to_vec()
}
#[wasm_bindgen]
pub fn load_policy_json(&mut self, json: &str) -> Result<(), JsValue> {
let policy: crate::policy::inference::InferenceModel = serde_json::from_str(json)
.map_err(|e| JsValue::from_str(&format!("Failed to parse policy JSON: {}", e)))?;
#[cfg(feature = "wasm")]
{
use web_sys::console;
console::log_1(
&format!(
"Model loaded: obs_dim={}, action_dim={}, hidden_dim={}, activation={:?}",
policy.obs_dim, policy.action_dim, policy.hidden_dim, policy.activation
)
.into(),
);
}
self.policy = Some(policy);
Ok(())
}
#[wasm_bindgen]
pub fn get_policy_action(&self) -> i32 {
if let Some(ref policy) = self.policy {
let state = self.env.get_state();
#[cfg(feature = "wasm")]
{
use web_sys::console;
console::log_1(&format!("State: {:?}", state).into());
}
let action = policy.get_action(&state) as i32;
#[cfg(feature = "wasm")]
{
use web_sys::console;
console::log_1(&format!("Action: {}", action).into());
}
action
} else {
-1 }
}
#[wasm_bindgen]
pub fn has_policy(&self) -> bool {
self.policy.is_some()
}
}
#[cfg(feature = "wasm")]
#[wasm_bindgen]
pub struct WasmSnake {
env: SnakeEnv,
episode: u32,
policy: Option<crate::inference::snake::SnakeCNNInference>,
}
#[cfg(feature = "wasm")]
#[wasm_bindgen]
impl WasmSnake {
#[wasm_bindgen(constructor)]
pub fn new(width: i32, height: i32, num_agents: usize) -> Self {
console_error_panic_hook::set_once();
Self { env: SnakeEnv::new_multi(width, height, num_agents), episode: 0, policy: None }
}
#[wasm_bindgen]
pub fn reset(&mut self) {
self.env.reset();
self.episode += 1;
}
#[wasm_bindgen]
pub fn step(&mut self, actions: &[i32]) {
let actions_i64: Vec<i64> = actions.iter().map(|&a| a as i64).collect();
let _ = self.env.step_multi(&actions_i64);
}
#[wasm_bindgen]
pub fn get_observation(&self, agent_id: usize) -> Vec<f32> {
self.env.get_grid_observation(agent_id)
}
#[wasm_bindgen]
pub fn num_agents(&self) -> usize {
self.env.num_agents
}
#[wasm_bindgen]
pub fn active_agents(&self) -> Vec<u8> {
self.env.snakes.iter().map(|s| if s.is_alive() { 1 } else { 0 }).collect()
}
#[wasm_bindgen]
pub fn get_width(&self) -> i32 {
self.env.width
}
#[wasm_bindgen]
pub fn get_height(&self) -> i32 {
self.env.height
}
#[wasm_bindgen]
pub fn get_snake_positions(&self) -> Vec<i32> {
let mut positions = Vec::new();
positions.push(self.env.snakes.len() as i32);
for snake in &self.env.snakes {
positions.push(snake.body.len() as i32);
for pos in &snake.body {
positions.push(pos.x);
positions.push(pos.y);
}
}
positions
}
#[wasm_bindgen]
pub fn get_food_positions(&self) -> Vec<i32> {
vec![self.env.food.x, self.env.food.y]
}
#[wasm_bindgen]
pub fn get_episode(&self) -> u32 {
self.episode
}
#[wasm_bindgen]
pub fn load_policy_json(&mut self, json: &str) -> Result<(), JsValue> {
let policy: crate::inference::snake::SnakeCNNInference = serde_json::from_str(json)
.map_err(|e| JsValue::from_str(&format!("Failed to parse policy JSON: {}", e)))?;
self.policy = Some(policy);
Ok(())
}
#[wasm_bindgen]
pub fn get_policy_action(&self, agent_id: usize) -> i32 {
if let Some(ref policy) = self.policy {
let observation = self.env.get_grid_observation(agent_id);
policy.get_action(&observation) as i32
} else {
-1 }
}
#[wasm_bindgen]
pub fn has_policy(&self) -> bool {
self.policy.is_some()
}
}
#[cfg(feature = "wasm")]
#[wasm_bindgen]
pub struct WasmPong {
env: Pong,
episode: u32,
episode_steps: u32,
policy: Option<crate::policy::inference::InferenceModel>,
}
#[cfg(feature = "wasm")]
#[wasm_bindgen]
impl WasmPong {
#[wasm_bindgen(constructor)]
pub fn new() -> Self {
console_error_panic_hook::set_once();
let mut env = Pong::new();
env.reset();
Self { env, episode: 0, episode_steps: 0, policy: None }
}
#[wasm_bindgen]
pub fn reset(&mut self) -> Vec<f32> {
self.env.reset();
self.episode += 1;
self.episode_steps = 0;
self.env.get_state().to_vec()
}
#[wasm_bindgen]
pub fn step(&mut self, action: i32) -> Vec<f32> {
let result = self.env.step(action as i64);
self.episode_steps += 1;
let mut output = self.env.get_state().to_vec();
output.push(result.reward);
output.push(if result.terminated { 1.0 } else { 0.0 });
output.push(if result.truncated { 1.0 } else { 0.0 });
output
}
#[wasm_bindgen]
pub fn get_state(&self) -> Vec<f32> {
self.env.get_state().to_vec()
}
#[wasm_bindgen]
pub fn get_episode(&self) -> u32 {
self.episode
}
#[wasm_bindgen]
pub fn get_steps(&self) -> u32 {
self.episode_steps
}
#[wasm_bindgen]
pub fn load_policy_json(&mut self, json: &str) -> Result<(), JsValue> {
let policy: crate::policy::inference::InferenceModel = serde_json::from_str(json)
.map_err(|e| JsValue::from_str(&format!("Failed to parse policy JSON: {}", e)))?;
self.policy = Some(policy);
Ok(())
}
#[wasm_bindgen]
pub fn get_policy_action(&self) -> i32 {
if let Some(ref policy) = self.policy {
let obs = self.env.get_observation();
policy.get_action(&obs) as i32
} else {
-1
}
}
#[wasm_bindgen]
pub fn has_policy(&self) -> bool {
self.policy.is_some()
}
}
#[cfg(feature = "wasm")]
#[wasm_bindgen]
pub struct WasmSimpleBandit {
env: SimpleBandit,
episode: u32,
episode_steps: u32,
total_reward: f64,
successes: u32,
}
#[cfg(feature = "wasm")]
#[wasm_bindgen]
impl WasmSimpleBandit {
#[wasm_bindgen(constructor)]
pub fn new() -> Self {
console_error_panic_hook::set_once();
Self {
env: SimpleBandit::new(),
episode: 0,
episode_steps: 0,
total_reward: 0.0,
successes: 0,
}
}
#[wasm_bindgen]
pub fn reset(&mut self) -> Vec<f32> {
self.env.reset();
self.episode += 1;
self.episode_steps = 0;
self.successes = 0;
self.env.get_observation()
}
#[wasm_bindgen]
pub fn step(&mut self, action: i32) -> Vec<f32> {
let result = self.env.step(action as i64);
self.episode_steps += 1;
self.total_reward += result.reward as f64;
if result.reward > 0.0 {
self.successes += 1;
}
let mut output = result.observation.clone();
output.push(result.reward);
output.push(if result.terminated { 1.0 } else { 0.0 });
output
}
#[wasm_bindgen]
pub fn get_episode(&self) -> u32 {
self.episode
}
#[wasm_bindgen]
pub fn get_steps(&self) -> u32 {
self.episode_steps
}
#[wasm_bindgen]
pub fn get_success_rate(&self) -> f32 {
if self.episode_steps == 0 {
0.0
} else {
(self.successes as f32 / self.episode_steps as f32) * 100.0
}
}
#[wasm_bindgen]
pub fn get_total_reward(&self) -> f32 {
self.total_reward as f32
}
#[wasm_bindgen]
pub fn get_state(&self) -> Vec<f32> {
self.env.get_observation()
}
}
#[cfg(feature = "wasm")]
use console_error_panic_hook;