core_rust/core/game/
game_logic.rs

1use crate::core::game::utils::{actions, errors, unit, world};
2use actions::Action;
3use errors::GameError;
4use unit::Unit;
5use world::{Tile, World};
6
7use crate::core::game::{game_variables, state};
8use game_variables::GameVars;
9use state::State;
10
11/// Deterministic GridWorld with a single unit and goal tile.
12#[derive(Debug, Clone)]
13pub struct Game {
14    _world_configuration: Vec<Vec<char>>,
15    world: World,
16    unit: Unit,
17    turn: i32,
18}
19
20impl Game {
21    /// Construct a `Game` from a 2D character grid.
22    pub fn new(world_vector: Vec<Vec<char>>) -> Result<Self, GameError> {
23        let mut world = World::new(&world_vector)?;
24
25        let unit = Unit::new();
26
27        world.place_unit(unit);
28
29        Ok(Game {
30            world,
31            unit,
32            _world_configuration: world_vector,
33            turn: 0,
34        })
35    }
36
37    /// Borrow a tile at coordinates `(x, y)`.
38    pub fn tile(&mut self, x: usize, y: usize) -> &Tile {
39        self.world.get_tile(x, y)
40    }
41
42    /// Pretty-print the grid with coordinates.
43    pub fn print(&self) {
44        println!("T: {}", self.turn);
45        self.world.print();
46    }
47
48    /// Width/height of the square world.
49    pub fn get_size(&self) -> usize {
50        self.world.size
51    }
52
53    /// Reset unit position and turn counter to the initial world configuration.
54    pub fn reset(&mut self) {
55        // If we generated once, re-generation must also succeed.
56        self.world = World::new(&self._world_configuration).unwrap();
57        self.unit = Unit::new();
58        self.world.place_unit(self.unit);
59        self.turn = 0;
60    }
61
62    /// Whether the unit is currently on the goal tile.
63    pub fn check_game_done(&self) -> bool {
64        self.unit.get_position() == self.world.goal
65    }
66
67    /// Reward is 1.0 on reaching the goal and 0.0 otherwise.
68    fn get_score(&self) -> f32 {
69        if self.unit.get_position() == self.world.goal {
70            return 1.0;
71        }
72        0.0
73    }
74
75    /// Coordinates of the goal tile.
76    pub fn goal(&self) -> (usize, usize) {
77        self.world.goal
78    }
79
80    /// Return a clone of the original world configuration.
81    pub fn world_configuration(&self) -> Vec<Vec<char>> {
82        self._world_configuration.clone()
83    }
84
85    /// Update the unit position given a ground action (if legal).
86    fn move_unit(&mut self, action: &Action) -> Result<(), GameError> {
87        if self.unit.get_movement() <= 0 {
88            return Ok(());
89        }
90
91        let (dx, dy): (isize, isize) = match action {
92            Action::Up => (0, -1),
93            Action::Down => (0, 1),
94            Action::Left => (-1, 0),
95            Action::Right => (1, 0),
96            _ => {
97                return Err(GameError::InvalidAction { action: *action });
98            }
99        };
100
101        let (old_x, old_y) = self.unit.get_position();
102        let (new_x, new_y) = (old_x as isize + dx, old_y as isize + dy);
103
104        // If moving out of bounds, consume movement and stay in place
105        if new_x < 0
106            || new_y < 0
107            || new_x >= self.world.size as isize
108            || new_y >= self.world.size as isize
109        {
110            self.unit.deduct_movement();
111            return Ok(());
112        }
113        // New position is within bounds, try to move the unit
114        let (new_x_u, new_y_u) = (new_x as usize, new_y as usize);
115        if self.world.get_tile(new_x_u, new_y_u).is_walkable() {
116            match self.world.remove_unit() {
117                Ok(()) => {
118                    self.unit.set_position(new_x_u, new_y_u);
119                    self.world.place_unit(self.unit);
120                }
121                Err(e) => {
122                    return Err(e);
123                }
124            }
125        }
126
127        Ok(())
128    }
129
130    /// Build a `State` with the unit position and cardinal moves.
131    pub fn get_state(&self) -> State {
132        let valid_moves = vec![Action::Up, Action::Down, Action::Left, Action::Right];
133        let unit_position = self.unit.get_position();
134        State::new(unit_position, valid_moves)
135    }
136
137    /// Apply an action in-place and return the next state and game variables.
138    pub fn step(&mut self, action: &Action) -> Result<(State, GameVars), GameError> {
139        match self.move_unit(action) {
140            Ok(()) => {
141                self.turn += 1;
142                self.unit.reset_movement();
143                let return_tuple = (
144                    self.get_state(),
145                    GameVars::new(self.turn, self.get_score(), self.check_game_done()),
146                );
147                Ok(return_tuple)
148            }
149            Err(e) => Err(e),
150        }
151    }
152
153    /// Set the unit position to match a given state.
154    fn set_state(&mut self, state: &State) -> Result<(), GameError> {
155        let (new_x, new_y) = state.unit_position;
156        match self.world.remove_unit() {
157            Ok(()) => {
158                self.unit.set_position(new_x, new_y);
159                self.world.place_unit(self.unit);
160                self.unit.reset_movement();
161                Ok(())
162            }
163            Err(e) => Err(e),
164        }
165    }
166
167    /// Simulate a step from `initial_state` with `action` without mutating `self`.
168    pub fn simulate(
169        &self,
170        initial_state: &State,
171        action: &Action,
172    ) -> Result<(State, GameVars), GameError> {
173        let mut copied_game = self.clone();
174        match copied_game.set_state(initial_state) {
175            Ok(()) => match copied_game.step(action) {
176                Ok((state, game_variables)) => Ok((state, game_variables)),
177                Err(e) => Err(e),
178            },
179            Err(e) => Err(e),
180        }
181    }
182}