thrust_rl/env/games/
simple_bandit.rs1use rand::{Rng, SeedableRng};
15
16use crate::env::{Environment, SpaceInfo, SpaceType, StepInfo, StepResult};
17
18#[derive(Debug, Clone)]
27pub struct SimpleBanditState {
28 pub state: f32,
30 pub steps: usize,
32}
33
34#[derive(Debug)]
46pub struct SimpleBandit {
47 state: f32,
48 steps: usize,
49 max_steps: usize,
50 rng: rand::rngs::StdRng,
51}
52
53impl SimpleBandit {
54 pub fn new() -> Self {
56 Self { state: 0.0, steps: 0, max_steps: 100, rng: rand::rngs::StdRng::from_os_rng() }
57 }
58}
59
60impl Default for SimpleBandit {
61 fn default() -> Self {
62 Self::new()
63 }
64}
65
66impl Environment for SimpleBandit {
67 type Action = i64;
68 type State = SimpleBanditState;
69
70 fn reset(&mut self) {
71 self.state = self.rng.random_range(0..2) as f32;
73 self.steps = 0;
74 }
75
76 fn get_observation(&self) -> Vec<f32> {
77 vec![self.state]
78 }
79
80 fn step(&mut self, action: i64) -> StepResult {
81 let reward = if action == self.state as i64 {
83 1.0
84 } else {
85 0.0
86 };
87
88 self.steps += 1;
89 let terminated = self.steps >= self.max_steps;
90
91 self.state = self.rng.random_range(0..2) as f32;
93
94 StepResult {
95 observation: self.get_observation(),
96 reward,
97 terminated,
98 truncated: false,
99 info: StepInfo::default(),
100 }
101 }
102
103 fn observation_space(&self) -> SpaceInfo {
104 SpaceInfo {
105 shape: vec![1], space_type: SpaceType::Box,
107 }
108 }
109
110 fn action_space(&self) -> SpaceInfo {
111 SpaceInfo {
112 shape: vec![],
113 space_type: SpaceType::Discrete(2), }
115 }
116
117 fn render(&self) -> Vec<u8> {
118 Vec::new()
119 }
120
121 fn close(&mut self) {
122 }
124
125 fn clone_state(&self) -> SimpleBanditState {
126 SimpleBanditState { state: self.state, steps: self.steps }
127 }
128
129 fn restore_state(&mut self, state: &SimpleBanditState) {
130 self.state = state.state;
131 self.steps = state.steps;
132 }
133}
134
135#[cfg(test)]
136mod tests {
137 use super::*;
138
139 #[test]
140 fn test_simple_bandit_rewards() {
141 let mut env = SimpleBandit::new();
142 env.reset();
143
144 let initial_state = env.state as i64;
146 let result = env.step(initial_state);
147 assert_eq!(result.reward, 1.0);
148
149 env.state = 0.0;
151 let result = env.step(1);
152 assert_eq!(result.reward, 0.0);
153
154 env.state = 1.0;
155 let result = env.step(0);
156 assert_eq!(result.reward, 0.0);
157 }
158
159 #[test]
160 fn clone_restore_round_trips() {
161 let mut env = SimpleBandit::new();
162 env.reset();
163
164 for _ in 0..5 {
166 env.step(0);
167 }
168 let snap = env.clone_state();
169 let pre_state = env.state;
170 let pre_steps = env.steps;
171
172 let r1 = env.step(pre_state as i64);
174
175 env.restore_state(&snap);
177 assert_eq!(env.state, pre_state, "state must round-trip");
178 assert_eq!(env.steps, pre_steps, "steps must round-trip");
179
180 let r2 = env.step(pre_state as i64);
181
182 assert_eq!(r1.reward, r2.reward);
184 assert_eq!(r1.terminated, r2.terminated);
185 }
188
189 #[test]
190 fn test_episode_length() {
191 let mut env = SimpleBandit::new();
192 env.reset();
193
194 for i in 0..99 {
196 let result = env.step(0);
197 assert!(!result.terminated, "Episode terminated early at step {}", i);
198 }
199
200 let result = env.step(0);
201 assert!(result.terminated, "Episode should terminate at step 100");
202 }
203}