Skip to content

Commit

Permalink
Merge pull request #14 from pilksoc/backend
Browse files Browse the repository at this point in the history
Add Grid resize functionality
  • Loading branch information
ettolrach authored Mar 3, 2024
2 parents 584db21 + 7fba6f9 commit f793744
Show file tree
Hide file tree
Showing 5 changed files with 159 additions and 7 deletions.
1 change: 1 addition & 0 deletions backend/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ uuid = { version = "1.7.0", features = ["serde", "v5", "fast-rng", "macro-diagno
tokio = { version = "1", features = ["full"] }
tokio-stream = "0.1.6"
async-trait = "0.1.77"
thiserror = "1.0.57"
warp = "0.3"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
Expand Down
155 changes: 153 additions & 2 deletions backend/src/grid.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,27 @@
use std::collections::BTreeMap;
use crate::space::{ Space, SpaceKind };
use crate::space::Space;
use crate::Coordinate;
use thiserror::Error;

/// The direction in which to grow. For example, going from 3 to 9 would give a `GrowDirection::Expand`.
enum GrowDirection {
Shrink,
Expand,
}

/// An error when trying to expand or shrink the grid.
#[derive(Error, Debug)]
pub enum ResizeError {
#[error("At least one Kube which was formerly in the grid would not be in the grid after shrinking.")]
KubeWillBeOutOfBounds,
#[error("Grid width or height will exceed `u64::MAX`.")]
GridTooBig,
#[error("Grid width or height will be less than zero.")]
GridTooSmall,
}

/// A grid to represent the playing area. Only saves spaces which aren't empty.
#[derive(Debug, PartialEq)]
pub struct Grid {
spaces: BTreeMap<Coordinate, Space>,
width: u64,
Expand All @@ -28,6 +47,8 @@ impl Grid {
}

}

/// Adds a new space to the grid. In the future, this may return an [`std::result::Result::Err`] if the space is of type [`crate::space::SpaceKind::EmptySpace`].
pub fn insert(&mut self, space: Space) {
self.spaces.insert(space.coordinate, space);
}
Expand Down Expand Up @@ -78,19 +99,111 @@ impl Grid {
.collect::<Vec<_>>()
}

/// Returns neighbours which are in the grid *and* which aren't [`SpaceKind::EmptySpace`]s.
/// Returns neighbours which are in the grid *and* which aren't [`crate::space::SpaceKind::EmptySpace`]s.
pub fn get_nonempty_neighbours(&self, coordinate: Coordinate) -> Vec<&Space> {
self.neighbour_coords_in_bounds(coordinate).iter()
.map(|coord| self.get_space(*coord))
.flatten()
.collect()
}

/// Returns the grid size in the format `[width, height]`.
fn get_grid_size(&self) -> [u64; 2] {
[self.width, self.height]
}

fn change_grid_by_rings(&mut self, rings_to_change_by: u64, direction: GrowDirection) -> Result<(), ResizeError> {
// If the specified size is the same as the current size, no need to do anything.
if rings_to_change_by == 0 {
return Ok(());
}
let Some(double_rings) = rings_to_change_by.checked_mul(2) else {
return Err(ResizeError::GridTooBig)
};
let Some(new_width) = (match direction {
GrowDirection::Expand => self.width.checked_add(double_rings),
GrowDirection::Shrink => self.width.checked_sub(double_rings),
}) else {
return Err(ResizeError::GridTooBig);
};
let Some(new_height) = (match direction {
GrowDirection::Expand => self.height.checked_add(double_rings),
GrowDirection::Shrink => self.height.checked_sub(double_rings),
}) else {
return Err(ResizeError::GridTooBig);
};

// Careful! If the map is big, this may use a lot of memory!
let mut spaces_in_map: Vec<Space> = self.spaces.clone().into_values().collect();
for space in &mut spaces_in_map {
space.coordinate[0] = match direction {
GrowDirection::Expand => {
match space.coordinate[0].checked_add(rings_to_change_by) {
Some(n) => n,
None => return Err(ResizeError::KubeWillBeOutOfBounds),
}
},
GrowDirection::Shrink => {
match space.coordinate[0].checked_sub(rings_to_change_by) {
Some(n) => n,
None => return Err(ResizeError::KubeWillBeOutOfBounds),
}
}
};
space.coordinate[1] = match direction {
GrowDirection::Expand => {
match space.coordinate[1].checked_add(rings_to_change_by) {
Some(n) => n,
None => return Err(ResizeError::KubeWillBeOutOfBounds),
}
},
GrowDirection::Shrink => {
match space.coordinate[1].checked_sub(rings_to_change_by) {
Some(n) => n,
None => return Err(ResizeError::KubeWillBeOutOfBounds),
}
}
};
}
self.spaces = BTreeMap::new();
for space in spaces_in_map {
self.insert(space);
}
self.width = new_width;
self.height = new_height;
Ok(())

}

/// This will expand the grid size and change the coordinates of the respective kubes.
///
/// The change in size can be thought of as "rings of squares" to be added around the outside of the grid. So if the grid used to be a 2×2 grid, adding a ring of squares around the outside will give a 4×4 square.
pub fn expand_grid(&mut self, rings_to_add: u64) -> Result<(), ResizeError> {
self.change_grid_by_rings(rings_to_add, GrowDirection::Expand)
}

/// Like [`crate::grid::Grid::expand_grid`], but instead of expanding, this shrinks.
pub fn shrink_grid(&mut self, rings_to_shrink_by: u64) -> Result<(), ResizeError> {
self.change_grid_by_rings(rings_to_shrink_by, GrowDirection::Shrink)
}
}

#[cfg(test)]
mod tests {
use super::*;
use crate::space::{Space, SpaceKind};
fn grid_2x2() -> Grid {
let mut grid = Grid::new(2, 2);
let spaces = [
Space::new([0, 0], SpaceKind::EmptySpace),
Space::new([0, 1], SpaceKind::EmptySpace),
Space::new([1, 1], SpaceKind::EmptySpace),
];
for space in spaces {
grid.insert(space);
}
grid
}
fn grid_3x3() -> Grid {
let mut grid = Grid::new(3, 3);
let spaces = [
Expand All @@ -111,4 +224,42 @@ mod tests {
let neighbours = grid.get_nonempty_neighbours([0, 2]);
assert_eq!(vec![&Space::new([0, 1], SpaceKind::EmptySpace)], neighbours);
}

#[test]
fn increase_2_to_4() {
let mut grid = grid_2x2();
let _ = grid.expand_grid(1);
let mut expected_grid = Grid::new(4, 4);
let spaces = [
Space::new([1, 1], SpaceKind::EmptySpace),
Space::new([1, 2], SpaceKind::EmptySpace),
Space::new([2, 2], SpaceKind::EmptySpace),
];
for space in spaces {
expected_grid.insert(space);
}
assert_eq!(expected_grid, grid);
}
#[test]
fn decrease_from_3_to_1() {
let mut grid = Grid::new(3, 3);
grid.insert(Space::new([1, 1], SpaceKind::EmptySpace));
let _ = grid.shrink_grid(1);
let mut expected_grid = Grid::new(1, 1);
let spaces = [
Space::new([0, 0], SpaceKind::EmptySpace),
];
for space in spaces {
expected_grid.insert(space);
}
assert_eq!(expected_grid, grid);
}
#[test]
fn fail_on_invalid_grid_size_change() {
let mut grid = grid_3x3();
let shrink_res = grid.shrink_grid(1);
assert!(shrink_res.is_err());
let expand_res = grid.expand_grid(u64::MAX - 1);
assert!(expand_res.is_err());
}
}
4 changes: 2 additions & 2 deletions backend/src/kube.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use uuid::Uuid;
use serde::{Deserialize, Serialize};

#[derive(Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Debug)]
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Serialize, Deserialize)]
pub struct KubeId {
uuid: Uuid,
}
Expand All @@ -24,7 +24,7 @@ impl KubeId {
}
}

#[derive(PartialEq, Debug, Serialize, Deserialize)]
#[derive(PartialEq, Debug, Clone, Serialize, Deserialize)]
pub struct Kube {
pub id: KubeId,
pub name: String,
Expand Down
2 changes: 1 addition & 1 deletion backend/src/player.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#[derive(Debug, PartialEq)]
#[derive(Debug, PartialEq, Clone)]
pub struct Player {
uuid: String,
username: String,
Expand Down
4 changes: 2 additions & 2 deletions backend/src/space.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use crate::kube;
use crate::player;
use crate::Coordinate;

#[derive(Debug, PartialEq)]
#[derive(Debug, PartialEq, Clone)]
pub struct Space {
pub coordinate: Coordinate,
pub contains: SpaceKind,
Expand All @@ -16,7 +16,7 @@ impl Space {
}
}

#[derive(Debug, PartialEq)]
#[derive(Debug, PartialEq, Clone)]
#[allow(clippy::module_name_repetitions)]
pub enum SpaceKind {
Kube(kube::Kube),
Expand Down

0 comments on commit f793744

Please sign in to comment.