use crate::env::{
Environment, SpaceInfo, SpaceType, StepResult,
games::cartpole::{CartPole, CartPoleState},
};
#[derive(Debug, Default)]
pub struct MaskedCartPole {
inner: CartPole,
}
impl MaskedCartPole {
pub fn new() -> Self {
Self { inner: CartPole::new() }
}
fn mask(full: &[f32]) -> Vec<f32> {
vec![full[0], full[2]]
}
}
impl Environment for MaskedCartPole {
type Action = i64;
type State = CartPoleState;
fn reset(&mut self) {
self.inner.reset();
}
fn get_observation(&self) -> Vec<f32> {
Self::mask(&Environment::get_observation(&self.inner))
}
fn step(&mut self, action: i64) -> StepResult {
let mut result = self.inner.step(action);
result.observation = Self::mask(&result.observation);
result
}
fn observation_space(&self) -> SpaceInfo {
SpaceInfo { shape: vec![2], space_type: SpaceType::Box }
}
fn action_space(&self) -> SpaceInfo {
self.inner.action_space()
}
fn render(&self) -> Vec<u8> {
self.inner.render()
}
fn close(&mut self) {
self.inner.close();
}
fn clone_state(&self) -> CartPoleState {
self.inner.clone_state()
}
fn restore_state(&mut self, state: &CartPoleState) {
self.inner.restore_state(state);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_observation_is_two_dimensional() {
let env = MaskedCartPole::new();
let obs_space = env.observation_space();
assert_eq!(obs_space.shape, vec![2], "masked obs must be 2-D");
assert!(matches!(obs_space.space_type, SpaceType::Box));
}
#[test]
fn test_get_observation_drops_velocities() {
let mut env = MaskedCartPole::new();
env.reset();
let obs = env.get_observation();
assert_eq!(obs.len(), 2, "masked observation should have 2 elements");
}
#[test]
fn test_step_returns_masked_observation() {
let mut env = MaskedCartPole::new();
env.reset();
let result = env.step(1);
assert_eq!(result.observation.len(), 2, "stepped observation should be masked to 2-D");
assert!(result.reward == 0.0 || result.reward == 1.0, "reward inherited from CartPole");
}
#[test]
fn test_action_space_delegates() {
let env = MaskedCartPole::new();
let action_space = env.action_space();
assert!(matches!(action_space.space_type, SpaceType::Discrete(2)));
}
#[test]
fn test_masked_value_matches_full_positions() {
let mut env = MaskedCartPole::new();
env.reset();
let full = Environment::get_observation(&env.inner);
let masked = env.get_observation();
assert_eq!(masked[0], full[0], "index 0 = cart position");
assert_eq!(masked[1], full[2], "index 1 = pole angle");
}
#[test]
fn test_hundred_random_steps_no_panic() {
let mut env = MaskedCartPole::new();
env.reset();
for i in 0..100 {
let result = env.step((i % 2) as i64);
assert_eq!(result.observation.len(), 2);
if result.terminated || result.truncated {
env.reset();
}
}
}
}