diff --git a/client/wasm/src/lib.rs b/client/wasm/src/lib.rs index 0d08bc8..8e1551c 100644 --- a/client/wasm/src/lib.rs +++ b/client/wasm/src/lib.rs @@ -3,8 +3,8 @@ mod utils; use pong::collision::collision::{Collision, CollisionDetector}; use pong::game_field::{Field, Input, InputType}; use pong::game_object::game_object::{DefaultGameObject, GameObject}; -use pong::geom::vector::Vector; use pong::geom::shape::ShapeType; +use pong::geom::vector::Vector; use pong::pong::pong_events::DefaultPongEventWriter; use pong::utils::utils::{DefaultLoggerFactory, Logger}; use serde::{Deserialize, Serialize}; diff --git a/pong/src/collision.rs b/pong/src/collision.rs index 6c9baba..65af21b 100644 --- a/pong/src/collision.rs +++ b/pong/src/collision.rs @@ -1,9 +1,8 @@ -pub mod collision { +pub mod detection { + use crate::collision::collision::{Collision, CollisionRegistry, Collisions}; use crate::game_object::game_object::GameObject; use crate::utils::utils::{Logger, LoggerFactory}; - use std::cell::{RefCell}; - use std::collections::HashMap; - use std::fmt::Debug; + use std::cell::RefCell; use std::rc::Rc; pub struct CollisionDetectorConfig { @@ -86,84 +85,157 @@ pub mod collision { } } - pub trait CollisionRegistry: Debug { - fn get_collisions(&self) -> Vec<&Collision>; - fn get_collisions_by_id(&self, id: u16) -> Vec<&Collision>; - } + #[cfg(test)] + mod tests { + use crate::collision::collision::Collision; + use crate::collision::detection::{CollisionDetector, CollisionGroup}; + use crate::game_object::game_object::GameObject; + use crate::geom::shape::ShapeType; + use crate::geom::utils::BoundingBox; + use crate::geom::vector::Vector; + use crate::utils::utils::DefaultLoggerFactory; + use rstest::rstest; + use std::cell::RefCell; + use std::rc::Rc; - #[derive(Debug)] - pub struct Collisions { - pub state: Vec, - } - - impl Collisions { - pub fn new(collisions: Vec) -> Collisions { - Collisions { state: collisions } + #[rstest] + #[case(vec![], vec![])] + #[case( + vec![ + MockGameObject::new(1, "a", BoundingBox::create(&Vector{x: 50., y: 50.}, 20., 20.)), + MockGameObject::new(2, "b", BoundingBox::create(&Vector{x: 50., y: 50.}, 20., 20.)) + ], + vec![Collision(1, 2)] + )] + #[case( + vec![ + MockGameObject::new(1, "a", BoundingBox::create(&Vector{x: 60., y: 65.}, 20., 20.)), + MockGameObject::new(2, "b", BoundingBox::create(&Vector{x: 50., y: 50.}, 20., 20.)), + ], + vec![Collision(1, 2)] + )] + #[case( + vec![ + MockGameObject::new(1, "a", BoundingBox::create(&Vector{x: 50., y: 50.}, 20., 20.)), + MockGameObject::new(2, "b", BoundingBox::create(&Vector{x: 80., y: 80.}, 20., 20.)), + ], + vec![] + )] + #[case( + vec![ + MockGameObject::new(1, "a", BoundingBox::create(&Vector{x: 50., y: 50.}, 50., 50.)), + MockGameObject::new(2, "b", BoundingBox::create(&Vector{x: 500., y: 50.}, 50., 50.)), + ], + vec![] + )] + #[case( + vec![ + MockGameObject::new(1, "a", BoundingBox::create(&Vector{x: 60., y: 65.}, 20., 20.)), + MockGameObject::new(2, "c", BoundingBox::create(&Vector{x: 50., y: 50.}, 20., 20.)), + ], + vec![] + )] + pub fn should_detect_collisions( + #[case] objs: Vec>>>, + #[case] expected_collisions: Vec, + ) { + let logger = DefaultLoggerFactory::noop(); + let mut detector = CollisionDetector::new(&logger); + detector.set_groups(vec![CollisionGroup(String::from("a"), String::from("b"))]); + let res = detector.detect_collisions(objs); + assert_eq!( + res.get_collisions(), + expected_collisions.iter().collect::>() + ); } - } - impl CollisionRegistry for Collisions { - fn get_collisions(&self) -> Vec<&Collision> { - self.state.iter().collect() + #[derive(Debug)] + pub struct MockGameObject { + id: u16, + obj_type: String, + bounding_box: BoundingBox, } - fn get_collisions_by_id(&self, id: u16) -> Vec<&Collision> { - self.state - .iter() - .filter(|c| c.0 == id || c.1 == id) - .collect() - } - } - pub struct CollisionHandlerRegistry { - handlers: HashMap< - (String, String), - fn(&Rc>>, &Rc>>), - >, - } - - type CollisionCallback = fn(&Rc>>, &Rc>>); - - impl CollisionHandlerRegistry { - pub fn new() -> CollisionHandlerRegistry { - CollisionHandlerRegistry { - handlers: HashMap::new(), + impl MockGameObject { + pub fn new( + id: u16, + obj_type: &str, + bounding_box: BoundingBox, + ) -> Rc>> { + Rc::new(RefCell::new(Box::new(MockGameObject { + id, + obj_type: String::from(obj_type), + bounding_box, + }))) } } - pub fn add(&mut self, mapping: (String, String), callback: CollisionCallback) { - if self.handlers.contains_key(&mapping) { - panic!( - "Collision handler for mapping {:?} is already registered.", - mapping - ) + impl GameObject for MockGameObject { + fn id(&self) -> u16 { + self.id } - self.handlers.insert(mapping, callback); - } - pub fn call( - &self, - mapping: &(String, String), - values: ( - &Rc>>, - &Rc>>, - ), - ) -> bool { - let regular = self.handlers.get(&mapping); - if let Some(callback) = regular { - callback(values.0, values.1); - return true; + fn obj_type(&self) -> &str { + &*self.obj_type } - let inverse = self.handlers.get(&(mapping.clone().1, mapping.clone().0)); - if let Some(callback) = inverse { - callback(values.1, values.0); - return true; + + fn shape(&self) -> &ShapeType { + todo!() + } + + fn pos(&self) -> &Vector { + todo!() + } + + fn pos_mut(&mut self) -> &mut Vector { + todo!() + } + + fn orientation(&self) -> &Vector { + todo!() + } + + fn orientation_mut(&mut self) -> &mut Vector { + todo!() + } + + fn update_pos(&mut self, _ms_diff: f64) { + todo!() + } + + fn bounding_box(&self) -> BoundingBox { + self.bounding_box.clone() + } + + fn vel(&self) -> &Vector { + todo!() + } + + fn vel_mut(&mut self) -> &mut Vector { + todo!() + } + + fn is_static(&self) -> bool { + todo!() + } + + fn is_dirty(&self) -> bool { + todo!() + } + + fn set_dirty(&mut self, _is_dirty: bool) { + todo!() } - return false; } } +} - #[derive(Debug, Eq, PartialEq)] - pub struct Collision(pub u16, pub u16); +pub mod handler { + use std::cell::RefCell; + use std::collections::HashMap; + use std::rc::Rc; + use crate::game_object::game_object::GameObject; + use crate::utils::utils::{Logger, LoggerFactory}; pub struct CollisionHandler { logger: Box, @@ -223,4 +295,171 @@ pub mod collision { // obj_a.pos_mut().add(&b_to_a); // } } + + pub struct CollisionHandlerRegistry { + handlers: HashMap< + (String, String), + fn(&Rc>>, &Rc>>), + >, + } + + type CollisionCallback = + fn(&Rc>>, &Rc>>); + + impl CollisionHandlerRegistry { + pub fn new() -> CollisionHandlerRegistry { + CollisionHandlerRegistry { + handlers: HashMap::new(), + } + } + + pub fn add(&mut self, mapping: (String, String), callback: CollisionCallback) { + if self.handlers.contains_key(&mapping) { + panic!( + "Collision handler for mapping {:?} is already registered.", + mapping + ) + } + self.handlers.insert(mapping, callback); + } + + pub fn call( + &self, + mapping: &(String, String), + values: ( + &Rc>>, + &Rc>>, + ), + ) -> bool { + let regular = self.handlers.get(&mapping); + if let Some(callback) = regular { + callback(values.0, values.1); + return true; + } + let inverse = self.handlers.get(&(mapping.clone().1, mapping.clone().0)); + if let Some(callback) = inverse { + callback(values.1, values.0); + return true; + } + return false; + } + } + + #[cfg(test)] + mod tests { + use rstest::rstest; + use std::cell::RefCell; + use std::rc::Rc; + use crate::collision::handler::CollisionHandler; + use crate::game_object::components::{DefaultGeomComp, DefaultPhysicsComp}; + use crate::game_object::game_object::{DefaultGameObject, GameObject}; + use crate::geom::shape::Shape; + use crate::geom::vector::Vector; + use crate::utils::utils::DefaultLoggerFactory; + + #[rstest] + #[case( + create_game_obj(1, Vector::new(1., 0.), Vector::new(1., 0.), true), + create_game_obj(2, Vector::new(0., 0.), Vector::new(0., 1.), true) + )] + #[case( + create_game_obj(1, Vector::new(1., 0.), Vector::new(1., 0.), false), + create_game_obj(2, Vector::new(0., 0.), Vector::new(0., 1.), true) + )] + #[case( + create_game_obj(1, Vector::new(-1., 0.), Vector::new(-1., 0.), false), + create_game_obj(2, Vector::new(0., 0.), Vector::new(0., 1.), true), + )] + #[case( + create_game_obj(1, Vector::new(1., 1.), Vector::new(1., 1.), false), + create_game_obj(2, Vector::new(0., 0.), Vector::new(0., 1.), true) + )] + #[case( + create_game_obj(1, Vector::new(-2., 1.), Vector::new(-1., 0.), false), + create_game_obj(2, Vector::new(0., 0.), Vector::new(0., 1.), true), + )] + #[case( + create_game_obj(1, Vector::new(1., 0.), Vector::new(1., 0.), false), + create_game_obj(2, Vector::new(0., 1.), Vector::new(0., 1.), true) + )] + #[case( + create_game_obj(1, Vector::new(-2., 1.), Vector::new(-1., 0.), false), + create_game_obj(2, Vector::new(0., 0.), Vector::new(0., 1.), true), + )] + pub fn should_handle_collision( + #[case] obj_a: Rc>>, + #[case] obj_b: Rc>>, + ) { + let logger = DefaultLoggerFactory::noop(); + let mut handler = CollisionHandler::new(&logger); + handler.register((String::from("obj"), String::from("obj")), |_a, _b| { + let mut a_mut = RefCell::borrow_mut(_a); + let mut vel_inverted = a_mut.vel().clone(); + vel_inverted.invert(); + *a_mut.vel_mut() = vel_inverted; + }); + let expected_vel_a = Vector::inverted(RefCell::borrow(&obj_a).vel()); + let res = handler.handle(&obj_a, &obj_b); + assert_eq!(true, res); + assert_eq!(RefCell::borrow(&obj_a).pos(), RefCell::borrow(&obj_a).pos()); + assert_eq!(RefCell::borrow(&obj_a).vel(), &expected_vel_a); + assert_eq!(RefCell::borrow(&obj_b).pos(), RefCell::borrow(&obj_b).pos()); + assert_eq!(RefCell::borrow(&obj_a).vel(), RefCell::borrow(&obj_a).vel()); + } + + fn create_game_obj( + id: u16, + vel: Vector, + orientation: Vector, + is_static: bool, + ) -> Rc>> { + Rc::new(RefCell::new(Box::new(DefaultGameObject::new( + id, + "obj".to_string(), + Box::new(DefaultGeomComp::new(Shape::rect( + Vector::zero(), + orientation, + 20., + 20., + ))), + Box::new(DefaultPhysicsComp::new(vel, is_static)), + )))) + } + } +} + +pub mod collision { + use std::fmt::Debug; + + pub trait CollisionRegistry: Debug { + fn get_collisions(&self) -> Vec<&Collision>; + fn get_collisions_by_id(&self, id: u16) -> Vec<&Collision>; + } + + #[derive(Debug)] + pub struct Collisions { + pub state: Vec, + } + + impl Collisions { + pub fn new(collisions: Vec) -> Collisions { + Collisions { state: collisions } + } + } + + impl CollisionRegistry for Collisions { + fn get_collisions(&self) -> Vec<&Collision> { + self.state.iter().collect() + } + fn get_collisions_by_id(&self, id: u16) -> Vec<&Collision> { + self.state + .iter() + .filter(|c| c.0 == id || c.1 == id) + .collect() + } + } + + #[derive(Debug, Eq, PartialEq)] + pub struct Collision(pub u16, pub u16); + } diff --git a/pong/src/event.rs b/pong/src/event.rs index 7505cea..fb3eb75 100644 --- a/pong/src/event.rs +++ b/pong/src/event.rs @@ -37,7 +37,7 @@ pub mod event { pub struct NoopEventWriterImpl {} impl EventWriterImpl for NoopEventWriterImpl { fn write(&mut self, _event: Event) -> Result<(), String> { - todo!() + Ok(()) } } diff --git a/pong/src/game_field.rs b/pong/src/game_field.rs index 41358d6..aeb2df7 100644 --- a/pong/src/game_field.rs +++ b/pong/src/game_field.rs @@ -1,12 +1,14 @@ -use std::cell::{RefCell}; +use std::cell::RefCell; use std::rc::Rc; use crate::collision::collision::{ - CollisionDetector, CollisionGroup, CollisionHandler, CollisionRegistry, Collisions, + CollisionRegistry, Collisions, }; +use crate::collision::detection::{CollisionDetector, CollisionGroup}; +use crate::collision::handler::{CollisionHandler}; use crate::game_object::components::{DefaultGeomComp, DefaultPhysicsComp}; use crate::game_object::game_object::{DefaultGameObject, GameObject}; -use crate::geom::shape::{Shape}; +use crate::geom::shape::Shape; use crate::geom::vector::Vector; use crate::pong::pong_collisions::{ handle_ball_bounds_collision, handle_player_ball_collision, handle_player_bound_collision, @@ -191,15 +193,17 @@ impl Field { { for obj in self.objs.iter().filter(|o| RefCell::borrow(o).is_dirty()) { let mut obj = RefCell::borrow_mut(obj); - let event_write_res = self.event_writer - .write(PongEventType::GameObjUpdate(GameObjUpdate { - obj_id: &obj.id().to_string(), - vel: obj.vel(), - orientation: obj.orientation(), - pos: obj.pos(), - })); + let event_write_res = + self.event_writer + .write(PongEventType::GameObjUpdate(GameObjUpdate { + obj_id: &obj.id().to_string(), + vel: obj.vel(), + orientation: obj.orientation(), + pos: obj.pos(), + })); if let Err(e) = event_write_res { - self.logger.log(&*format!("Failed to write event logs: {}", e)) + self.logger + .log(&*format!("Failed to write event logs: {}", e)) } obj.set_dirty(false); } @@ -344,3 +348,49 @@ impl Bounds { self.1 } } + +#[cfg(test)] +mod tests { + use std::cell::RefCell; + use crate::game_field::{Field, Input, InputType}; + + #[test] + fn player_input_update_pos_up() { + let height = 1000; + let mut field = Field::mock(1000, height); + field.add_player(1, 50, height / 2); + let inputs = vec![Input { + input: InputType::UP, + obj_id: 1, + player: 1, + }]; + field.tick(inputs, 1.); + let player = RefCell::borrow( + field + .objs() + .iter() + .find(|o| RefCell::borrow(o).obj_type() == "player") + .unwrap(), + ); + assert_eq!(player.pos().y, 530.); + } + + #[test] + fn player_input_update_pos_down() { + let height = 1000; + let mut field = Field::mock(1000, height); + field.add_player(1, 50, height / 2); + let inputs = vec![Input { + input: InputType::DOWN, + obj_id: 1, + player: 1, + }]; + field.tick(inputs, 1.); + let objs = field.objs(); + let player = objs + .iter() + .find(|o| RefCell::borrow(o).obj_type() == "player") + .unwrap(); + assert_eq!(RefCell::borrow(player).pos().y, 470.); + } +} diff --git a/pong/src/game_object.rs b/pong/src/game_object.rs index 25565ea..6538d0c 100644 --- a/pong/src/game_object.rs +++ b/pong/src/game_object.rs @@ -1,9 +1,9 @@ pub mod game_object { use crate::game_object::components::{GeomComp, PhysicsComp}; - use crate::geom::geom::{BoundingBox}; - use crate::geom::shape::{ShapeType}; - use std::fmt::Debug; + use crate::geom::shape::ShapeType; + use crate::geom::utils::BoundingBox; use crate::geom::vector::Vector; + use std::fmt::Debug; pub trait GameObject: Debug { fn id(&self) -> u16; @@ -22,7 +22,6 @@ pub mod game_object { fn set_dirty(&mut self, is_dirty: bool); } - // #[derive(Clone, Debug, PartialEq)] #[derive(Debug)] pub struct DefaultGameObject { pub id: u16, @@ -120,16 +119,50 @@ pub mod game_object { self.dirty = is_dirty; } } + + #[cfg(test)] + mod tests { + use rstest::rstest; + use crate::game_object::components::{DefaultGeomComp, DefaultPhysicsComp}; + use crate::game_object::game_object::{DefaultGameObject, GameObject}; + use crate::geom::shape::Shape; + use crate::geom::vector::Vector; + + #[rstest] + #[case(Vector::new(100., 100.), Vector::new(-1., 1.), Vector::new(99.9, 100.1), 0.1)] + #[case(Vector::new(300., 400.), Vector::new(-5., 0.), Vector::new(299.5, 400.), 0.1)] + #[case(Vector::new(300., 400.), Vector::new(-5., 0.), Vector::new(299.935, 400.), 0.013)] + pub fn should_update_pos( + #[case] start_pos: Vector, + #[case] vel: Vector, + #[case] expected_pos: Vector, + #[case] ms_diff: f64, + ) { + let mut obj = DefaultGameObject::new( + 1, + "obj".to_string(), + Box::new(DefaultGeomComp::new(Shape::rect( + Vector::new(start_pos.x as f64, start_pos.y as f64), + Vector::new(1., 0.), + 0., + 0., + ))), + Box::new(DefaultPhysicsComp::new(vel, false)), + ); + obj.update_pos(ms_diff); + assert_eq!(*obj.pos(), expected_pos); + } + } } pub mod components { - use crate::geom::geom::{BoundingBox}; use crate::geom::shape::{ get_bounding_box, get_center, get_center_mut, get_orientation, get_orientation_mut, ShapeType, }; - use std::fmt::Debug; + use crate::geom::utils::BoundingBox; use crate::geom::vector::Vector; + use std::fmt::Debug; pub trait GeomComp: Debug { fn shape(&self) -> &ShapeType; diff --git a/pong/src/geom.rs b/pong/src/geom.rs index e3ec230..846da5b 100644 --- a/pong/src/geom.rs +++ b/pong/src/geom.rs @@ -1,5 +1,5 @@ pub mod vector { - use serde::{Serialize}; + use serde::Serialize; #[derive(Debug, Clone, Serialize)] pub struct Vector { @@ -165,9 +165,9 @@ pub mod vector { #[cfg(test)] mod tests { + use crate::geom::vector::Vector; use rstest::rstest; use std::f64::consts::FRAC_PI_4; - use crate::geom::vector::Vector; #[rstest] #[case(1., 0., 1.)] @@ -214,7 +214,10 @@ pub mod vector { #[case(Vector::new(1., 0.), Vector::new(0., -1.))] #[case(Vector::new(0., 1.), Vector::new(1., 0.))] #[case(Vector::new(7., 7.), Vector::new(7., -7.))] - pub fn should_get_orthogonal_clockwise(#[case] mut vector: Vector, #[case] expected: Vector) { + pub fn should_get_orthogonal_clockwise( + #[case] mut vector: Vector, + #[case] expected: Vector, + ) { vector.orthogonal_clockwise(); assert_eq!(vector, expected); } @@ -298,7 +301,7 @@ pub mod vector { } } -pub mod geom { +pub mod utils { use crate::geom::vector::Vector; #[derive(Clone, Debug)] @@ -399,12 +402,62 @@ pub mod geom { return false; } } + + #[cfg(test)] + mod tests { + use crate::geom::utils::BoundingBox; + use crate::geom::vector::Vector; + use rstest::rstest; + + #[rstest] + #[case(BoundingBox::create(&Vector::new(10., 10.), 5., 5.), Vector::new(10., 10.), true)] + #[case(BoundingBox::create(&Vector::new(10., 10.), 5., 5.), Vector::new(8., 8.), true)] + #[case(BoundingBox::create(&Vector::new(10., 10.), 5., 5.), Vector::new(20., 20.), false)] + pub fn should_correctly_determine_if_point_is_within_box( + #[case] bounding_box: BoundingBox, + #[case] point: Vector, + #[case] expected: bool, + ) { + let res = bounding_box.is_point_within(&point); + assert_eq!(res, expected); + } + + #[rstest] + #[case( + BoundingBox::create(&Vector::new(10., 10.), 5., 5.), + BoundingBox::create(&Vector::new(10., 10.), 5., 5.), + true + )] + #[case( + BoundingBox::create(&Vector::new(10., 10.), 5., 5.), + BoundingBox::create(&Vector::new(8., 8.), 5., 5.), + true + )] + #[case( + BoundingBox::create(&Vector::new(10., 10.), 5., 5.), + BoundingBox::create(&Vector::new(4.9, 4.9), 5., 5.), + false + )] + #[case( + BoundingBox::create(&Vector::new(10., 10.), 5., 5.), + BoundingBox::create(&Vector::new(5., 5.), 5., 5.), + true + )] + pub fn should_correctly_determine_if_overlap( + #[case] bounding_box_a: BoundingBox, + #[case] bounding_box_b: BoundingBox, + #[case] expected: bool, + ) { + let res = bounding_box_a.overlaps(&bounding_box_b); + assert_eq!(res, expected); + } + } } pub mod shape { - use crate::geom::geom::{BoundingBox}; - use std::fmt::Debug; + use crate::geom::utils::BoundingBox; use crate::geom::vector::Vector; + use std::fmt::Debug; #[derive(Clone, Debug, PartialEq)] pub enum ShapeType { diff --git a/pong/src/pong.rs b/pong/src/pong.rs index 18b9b94..539b5ea 100644 --- a/pong/src/pong.rs +++ b/pong/src/pong.rs @@ -1,8 +1,8 @@ pub mod pong_collisions { use crate::game_object::game_object::GameObject; - use crate::geom::vector::{Vector}; use crate::geom::shape::ShapeType; - use std::cell::{RefCell}; + use crate::geom::vector::Vector; + use std::cell::RefCell; use std::rc::Rc; pub fn handle_player_ball_collision( @@ -77,6 +77,84 @@ pub mod pong_collisions { player.set_dirty(true); } + + #[cfg(test)] + mod tests { + use rstest::rstest; + use std::cell::RefCell; + use std::rc::Rc; + use crate::game_field::{Bound, Field}; + use crate::game_object::game_object::{DefaultGameObject, GameObject}; + use crate::geom::vector::Vector; + use crate::pong::pong_collisions::handle_player_bound_collision; + use crate::pong::pong_events::NoopPongEventWriter; + use crate::utils::utils::DefaultLoggerFactory; + + #[rstest] + #[case( + // given + create_player(1, 10, 0, Vector::new(0., -1.)), + get_bound(Bound::BOTTOM), + // expected + create_player(1, 10, 61, Vector::new(0., -1.)), + get_bound(Bound::BOTTOM) + )] + #[case( + // given + create_player(1, 10, 1, Vector::new(0., -1.)), + get_bound(Bound::BOTTOM), + // expected + create_player(1, 10, 61, Vector::new(0., -1.)), + get_bound(Bound::BOTTOM) + )] + #[case( + // given + create_player(1, 10, 601, Vector::new(0., 1.)), + get_bound(Bound::TOP), + // expected + create_player(1, 10, 539, Vector::new(0., 1.)), + get_bound(Bound::TOP) + )] + #[case( + // given + create_player(1, 10, 599, Vector::new(0., 1.)), + get_bound(Bound::TOP), + // expected + create_player(1, 10, 539, Vector::new(0., 1.)), + get_bound(Bound::TOP) + )] + pub fn should_correctly_handle_player_bounds_collision( + #[case] player: Rc>>, + #[case] bounds: Rc>>, + #[case] player_expected: Rc>>, + #[case] bounds_expected: Rc>>, + ) { + handle_player_bound_collision(&player, &bounds); + assert_eq!(player_expected.borrow().pos(), player.borrow().pos()); + assert_eq!(bounds_expected.borrow().pos(), bounds.borrow().pos()); + } + + fn create_player(id: u16, x: u16, y: u16, orientation: Vector) -> Rc>> { + let logger = DefaultLoggerFactory::noop(); + let event_writer = NoopPongEventWriter::new(); + let field = Field::new(logger, event_writer); + let mut player = DefaultGameObject::player(id, x, y, &field); + let player_orientation = player.orientation_mut(); + player_orientation.x = orientation.x; + player_orientation.y = orientation.y; + Rc::new(RefCell::new(player)) + } + + fn get_bound(bound: Bound) -> Rc>> { + let logger = DefaultLoggerFactory::noop(); + let event_writer = NoopPongEventWriter::new(); + let field = Field::new(logger, event_writer); + let bounds = DefaultGameObject::bounds(field.width, field.height); + return Rc::new(RefCell::new( + bounds.into_iter().find(|b| b.0 == bound).unwrap().inner(), + )); + } + } } pub mod pong_events { diff --git a/pong/tests/bounding_box_tests.rs b/pong/tests/bounding_box_tests.rs deleted file mode 100644 index 13225b7..0000000 --- a/pong/tests/bounding_box_tests.rs +++ /dev/null @@ -1,46 +0,0 @@ -use pong::geom::geom::{BoundingBox}; -use rstest::rstest; -use pong::geom::vector::Vector; - -#[rstest] -#[case(BoundingBox::create(&Vector::new(10., 10.), 5., 5.), Vector::new(10., 10.), true)] -#[case(BoundingBox::create(&Vector::new(10., 10.), 5., 5.), Vector::new(8., 8.), true)] -#[case(BoundingBox::create(&Vector::new(10., 10.), 5., 5.), Vector::new(20., 20.), false)] -pub fn should_correctly_determine_if_point_is_within_box( - #[case] bounding_box: BoundingBox, - #[case] point: Vector, - #[case] expected: bool, -) { - let res = bounding_box.is_point_within(&point); - assert_eq!(res, expected); -} - -#[rstest] -#[case( - BoundingBox::create(&Vector::new(10., 10.), 5., 5.), - BoundingBox::create(&Vector::new(10., 10.), 5., 5.), - true -)] -#[case( - BoundingBox::create(&Vector::new(10., 10.), 5., 5.), - BoundingBox::create(&Vector::new(8., 8.), 5., 5.), - true -)] -#[case( - BoundingBox::create(&Vector::new(10., 10.), 5., 5.), - BoundingBox::create(&Vector::new(4.9, 4.9), 5., 5.), - false -)] -#[case( - BoundingBox::create(&Vector::new(10., 10.), 5., 5.), - BoundingBox::create(&Vector::new(5., 5.), 5., 5.), - true -)] -pub fn should_correctly_determine_if_overlap( - #[case] bounding_box_a: BoundingBox, - #[case] bounding_box_b: BoundingBox, - #[case] expected: bool, -) { - let res = bounding_box_a.overlaps(&bounding_box_b); - assert_eq!(res, expected); -} diff --git a/pong/tests/collision_handler_test.rs b/pong/tests/collision_handler_test.rs deleted file mode 100644 index 1f1eb96..0000000 --- a/pong/tests/collision_handler_test.rs +++ /dev/null @@ -1,78 +0,0 @@ -use pong::collision::collision::CollisionHandler; -use pong::game_object::components::{DefaultGeomComp, DefaultPhysicsComp}; -use pong::game_object::game_object::{DefaultGameObject, GameObject}; -use pong::geom::vector::Vector; -use pong::geom::shape::Shape; -use pong::utils::utils::DefaultLoggerFactory; -use rstest::rstest; -use std::cell::RefCell; -use std::rc::Rc; - -#[rstest] -#[case( - create_game_obj(1, Vector::new(1., 0.), Vector::new(1., 0.), true), - create_game_obj(2, Vector::new(0., 0.), Vector::new(0., 1.), true), -)] -#[case( - create_game_obj(1, Vector::new(1., 0.), Vector::new(1., 0.), false), - create_game_obj(2, Vector::new(0., 0.), Vector::new(0., 1.), true), -)] -#[case( - create_game_obj(1, Vector::new(-1., 0.), Vector::new(-1., 0.), false), - create_game_obj(2, Vector::new(0., 0.), Vector::new(0., 1.), true), -)] -#[case( - create_game_obj(1, Vector::new(1., 1.), Vector::new(1., 1.), false), - create_game_obj(2, Vector::new(0., 0.), Vector::new(0., 1.), true), -)] -#[case( - create_game_obj(1, Vector::new(-2., 1.), Vector::new(-1., 0.), false), - create_game_obj(2, Vector::new(0., 0.), Vector::new(0., 1.), true), -)] -#[case( - create_game_obj(1, Vector::new(1., 0.), Vector::new(1., 0.), false), - create_game_obj(2, Vector::new(0., 1.), Vector::new(0., 1.), true), -)] -#[case( - create_game_obj(1, Vector::new(-2., 1.), Vector::new(-1., 0.), false), - create_game_obj(2, Vector::new(0., 0.), Vector::new(0., 1.), true), -)] -pub fn should_handle_collision( - #[case] obj_a: Rc>>, - #[case] obj_b: Rc>> -) { - let logger = DefaultLoggerFactory::noop(); - let mut handler = CollisionHandler::new(&logger); - handler.register((String::from("obj"), String::from("obj")), |_a, _b| { - let mut a_mut = RefCell::borrow_mut(_a); - let mut vel_inverted = a_mut.vel().clone(); - vel_inverted.invert(); - *a_mut.vel_mut() = vel_inverted; - }); - let expected_vel_a = Vector::inverted(RefCell::borrow(&obj_a).vel()); - let res = handler.handle(&obj_a, &obj_b); - assert_eq!(true, res); - assert_eq!(RefCell::borrow(&obj_a).pos(), RefCell::borrow(&obj_a).pos()); - assert_eq!(RefCell::borrow(&obj_a).vel(), &expected_vel_a); - assert_eq!(RefCell::borrow(&obj_b).pos(), RefCell::borrow(&obj_b).pos()); - assert_eq!(RefCell::borrow(&obj_a).vel(), RefCell::borrow(&obj_a).vel()); -} - -fn create_game_obj( - id: u16, - vel: Vector, - orientation: Vector, - is_static: bool, -) -> Rc>> { - Rc::new(RefCell::new(Box::new(DefaultGameObject::new( - id, - "obj".to_string(), - Box::new(DefaultGeomComp::new(Shape::rect( - Vector::zero(), - orientation, - 20., - 20., - ))), - Box::new(DefaultPhysicsComp::new(vel, is_static)), - )))) -} diff --git a/pong/tests/collision_tests.rs b/pong/tests/collision_tests.rs deleted file mode 100644 index d4e31d5..0000000 --- a/pong/tests/collision_tests.rs +++ /dev/null @@ -1,139 +0,0 @@ -use pong::collision::collision::{Collision, CollisionDetector, CollisionGroup}; -use pong::game_object::game_object::GameObject; -use pong::geom::geom::{BoundingBox}; -use pong::geom::shape::ShapeType; -use pong::utils::utils::DefaultLoggerFactory; -use rstest::rstest; -use std::cell::{RefCell}; -use std::rc::Rc; -use pong::geom::vector::Vector; - -#[rstest] -#[case(vec![], vec![])] -#[case( - vec![ - MockGameObject::new(1, "a", BoundingBox::create(&Vector{x: 50., y: 50.}, 20., 20.)), - MockGameObject::new(2, "b", BoundingBox::create(&Vector{x: 50., y: 50.}, 20., 20.)) - ], - vec![Collision(1, 2)] -)] -#[case( - vec![ - MockGameObject::new(1, "a", BoundingBox::create(&Vector{x: 60., y: 65.}, 20., 20.)), - MockGameObject::new(2, "b", BoundingBox::create(&Vector{x: 50., y: 50.}, 20., 20.)), - ], - vec![Collision(1, 2)] -)] -#[case( - vec![ - MockGameObject::new(1, "a", BoundingBox::create(&Vector{x: 50., y: 50.}, 20., 20.)), - MockGameObject::new(2, "b", BoundingBox::create(&Vector{x: 80., y: 80.}, 20., 20.)), - ], - vec![] -)] -#[case( - vec![ - MockGameObject::new(1, "a", BoundingBox::create(&Vector{x: 50., y: 50.}, 50., 50.)), - MockGameObject::new(2, "b", BoundingBox::create(&Vector{x: 500., y: 50.}, 50., 50.)), - ], - vec![] -)] -#[case( - vec![ - MockGameObject::new(1, "a", BoundingBox::create(&Vector{x: 60., y: 65.}, 20., 20.)), - MockGameObject::new(2, "c", BoundingBox::create(&Vector{x: 50., y: 50.}, 20., 20.)), - ], - vec![] -)] -pub fn should_detect_collisions( - #[case] objs: Vec>>>, - #[case] expected_collisions: Vec, -) { - let logger = DefaultLoggerFactory::noop(); - let mut detector = CollisionDetector::new(&logger); - detector.set_groups(vec![CollisionGroup(String::from("a"), String::from("b"))]); - let res = detector.detect_collisions(objs); - assert_eq!( - res.get_collisions(), - expected_collisions.iter().collect::>() - ); -} - -#[derive(Debug)] -pub struct MockGameObject { - id: u16, - obj_type: String, - bounding_box: BoundingBox -} - -impl MockGameObject { - pub fn new( - id: u16, - obj_type: &str, - bounding_box: BoundingBox, - ) -> Rc>> { - Rc::new(RefCell::new(Box::new(MockGameObject { - id, - obj_type: String::from(obj_type), - bounding_box - }))) - } -} - -impl GameObject for MockGameObject { - fn id(&self) -> u16 { - self.id - } - - fn obj_type(&self) -> &str { - &*self.obj_type - } - - fn shape(&self) -> &ShapeType { - todo!() - } - - fn pos(&self) -> &Vector { - todo!() - } - - fn pos_mut(&mut self) -> &mut Vector { - todo!() - } - - fn orientation(&self) -> &Vector { - todo!() - } - - fn orientation_mut(&mut self) -> &mut Vector { - todo!() - } - - fn update_pos(&mut self, _ms_diff: f64) { - todo!() - } - - fn bounding_box(&self) -> BoundingBox { - self.bounding_box.clone() - } - - fn vel(&self) -> &Vector { - todo!() - } - - fn vel_mut(&mut self) -> &mut Vector { - todo!() - } - - fn is_static(&self) -> bool { - todo!() - } - - fn is_dirty(&self) -> bool { - todo!() - } - - fn set_dirty(&mut self, _is_dirty: bool) { - todo!() - } -} diff --git a/pong/tests/game_field_test.rs b/pong/tests/game_field_test.rs deleted file mode 100644 index 9543e87..0000000 --- a/pong/tests/game_field_test.rs +++ /dev/null @@ -1,45 +0,0 @@ -#[cfg(test)] -mod game_field_tests { - use pong::game_field::{Field, Input, InputType}; - use std::cell::RefCell; - - #[test] - fn player_input_update_pos_up() { - let height = 1000; - let mut field = Field::mock(1000, height); - field.add_player(1, 50, height / 2); - let inputs = vec![Input { - input: InputType::UP, - obj_id: 1, - player: 1 - }]; - field.tick(inputs, 1_000.); - let player = RefCell::borrow( - field - .objs() - .iter() - .find(|o| RefCell::borrow(o).obj_type() == "player") - .unwrap(), - ); - assert_eq!(player.pos().y, height as f64 / 2. + 1.); - } - - #[test] - fn player_input_update_pos_down() { - let height = 1000; - let mut field = Field::mock(1000, height); - field.add_player(1, 50, height / 2); - let inputs = vec![Input { - input: InputType::DOWN, - obj_id: 1, - player: 1 - }]; - field.tick(inputs, 1_000.); - let objs = field.objs(); - let player = objs - .iter() - .find(|o| RefCell::borrow(o).obj_type() == "player") - .unwrap(); - assert_eq!(RefCell::borrow(player).pos().y, height as f64 / 2. - 1.); - } -} diff --git a/pong/tests/game_object_tests.rs b/pong/tests/game_object_tests.rs deleted file mode 100644 index b8dd47c..0000000 --- a/pong/tests/game_object_tests.rs +++ /dev/null @@ -1,30 +0,0 @@ -use pong::game_object::components::{DefaultGeomComp, DefaultPhysicsComp}; -use pong::game_object::game_object::{DefaultGameObject, GameObject}; -use pong::geom::vector::Vector; -use pong::geom::shape::Shape; -use rstest::rstest; - -#[rstest] -#[case(Vector::new(100., 100.), Vector::new(-1., 1.), Vector::new(99.9, 100.1), 0.1)] -#[case(Vector::new(300., 400.), Vector::new(-5., 0.), Vector::new(299.5, 400.), 0.1)] -#[case(Vector::new(300., 400.), Vector::new(-5., 0.), Vector::new(299.5, 400.), 0.013)] -pub fn should_update_pos( - #[case] start_pos: Vector, - #[case] vel: Vector, - #[case] expected_pos: Vector, - #[case] ms_diff: f64, -) { - let mut obj = DefaultGameObject::new( - 1, - "obj".to_string(), - Box::new(DefaultGeomComp::new(Shape::rect( - Vector::new(start_pos.x as f64, start_pos.y as f64), - Vector::new(1., 0.), - 0., - 0., - ))), - Box::new(DefaultPhysicsComp::new(vel, false)), - ); - obj.update_pos(ms_diff); - assert_eq!(*obj.pos(), expected_pos); -} diff --git a/pong/tests/pong_tests.rs b/pong/tests/pong_tests.rs index 44b9a06..e69de29 100644 --- a/pong/tests/pong_tests.rs +++ b/pong/tests/pong_tests.rs @@ -1,74 +0,0 @@ -use pong::game_field::{Bound, Field}; -use pong::game_object::game_object::{DefaultGameObject, GameObject}; -use pong::geom::vector::Vector; -use pong::pong::pong_collisions::handle_player_bound_collision; -use pong::utils::utils::{DefaultLoggerFactory}; -use rstest::rstest; -use std::cell::RefCell; -use std::rc::Rc; -use pong::pong::pong_events::NoopPongEventWriter; - -#[rstest] -#[case( - // given - create_player(1, 10, 0, Vector::new(0., -1.)), - get_bound(Bound::BOTTOM), - // expected - create_player(1, 10, 61, Vector::new(0., -1.)), - get_bound(Bound::BOTTOM) -)] -#[case( - // given - create_player(1, 10, 1, Vector::new(0., -1.)), - get_bound(Bound::BOTTOM), - // expected - create_player(1, 10, 61, Vector::new(0., -1.)), - get_bound(Bound::BOTTOM) -)] -#[case( - // given - create_player(1, 10, 601, Vector::new(0., 1.)), - get_bound(Bound::TOP), - // expected - create_player(1, 10, 539, Vector::new(0., 1.)), - get_bound(Bound::TOP) -)] -#[case( - // given - create_player(1, 10, 599, Vector::new(0., 1.)), - get_bound(Bound::TOP), - // expected - create_player(1, 10, 539, Vector::new(0., 1.)), - get_bound(Bound::TOP) -)] -pub fn should_correctly_handle_player_bounds_collision( - #[case] player: Rc>>, - #[case] bounds: Rc>>, - #[case] player_expected: Rc>>, - #[case] bounds_expected: Rc>>, -) { - handle_player_bound_collision(&player, &bounds); - assert_eq!(player_expected.borrow().pos(), player.borrow().pos()); - assert_eq!(bounds_expected.borrow().pos(), bounds.borrow().pos()); -} - -fn create_player(id: u16, x: u16, y: u16, orientation: Vector) -> Rc>> { - let logger = DefaultLoggerFactory::noop(); - let event_writer = NoopPongEventWriter::new(); - let field = Field::new(logger, event_writer); - let mut player = DefaultGameObject::player(id, x, y, &field); - let player_orientation = player.orientation_mut(); - player_orientation.x = orientation.x; - player_orientation.y = orientation.y; - Rc::new(RefCell::new(player)) -} - -fn get_bound(bound: Bound) -> Rc>> { - let logger = DefaultLoggerFactory::noop(); - let event_writer = NoopPongEventWriter::new(); - let field = Field::new(logger, event_writer); - let bounds = DefaultGameObject::bounds(field.width, field.height); - return Rc::new(RefCell::new( - bounds.into_iter().find(|b| b.0 == bound).unwrap().inner(), - )); -} diff --git a/pong/tests/vector_tests.rs b/pong/tests/vector_tests.rs deleted file mode 100644 index e69de29..0000000 diff --git a/server/src/http.rs b/server/src/http.rs index 2f7f1e9..f5af61e 100644 --- a/server/src/http.rs +++ b/server/src/http.rs @@ -2,14 +2,14 @@ use std::convert::Infallible; use std::net::SocketAddr; use std::str::FromStr; use std::sync::Arc; -use std::time::{Duration}; +use std::time::Duration; use futures::{sink::SinkExt, stream::StreamExt}; -use hyper::{Body, Method, Request, Response, Server, StatusCode}; use hyper::server::conn::AddrStream; use hyper::service::{make_service_fn, service_fn}; -use hyper_tungstenite::{HyperWebsocket}; +use hyper::{Body, Method, Request, Response, Server, StatusCode}; use hyper_tungstenite::tungstenite::{Error, Message}; +use hyper_tungstenite::HyperWebsocket; use serde::{Deserialize, Serialize}; use serde_json::json; use tokio::sync::Mutex; @@ -25,8 +25,16 @@ pub struct HttpServer { session_manager: Arc>, } impl HttpServer { - pub fn new(addr: [u8; 4], port: u16, kafka_host: &str, kafka_topic_manager_host: &str) -> HttpServer { - let session_manager = Arc::new(Mutex::new(SessionManager::new(kafka_host, kafka_topic_manager_host))); + pub fn new( + addr: [u8; 4], + port: u16, + kafka_host: &str, + kafka_topic_manager_host: &str, + ) -> HttpServer { + let session_manager = Arc::new(Mutex::new(SessionManager::new( + kafka_host, + kafka_topic_manager_host, + ))); HttpServer { addr, port, diff --git a/server/src/kafka.rs b/server/src/kafka.rs index 587b2b1..75be014 100644 --- a/server/src/kafka.rs +++ b/server/src/kafka.rs @@ -2,7 +2,7 @@ use std::str::FromStr; use std::time::Duration; use hyper::{Body, Client, Method, Request, Uri}; -use kafka::client::{ProduceMessage}; +use kafka::client::ProduceMessage; use kafka::consumer::{Consumer, FetchOffset, GroupOffsetStorage, MessageSet}; use kafka::producer::{Partitioner, Producer, Record, RequiredAcks, Topics}; use serde::Deserialize; diff --git a/server/src/main.rs b/server/src/main.rs index 3d4bd5f..3ec0268 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -14,12 +14,12 @@ pub async fn main() { let kafka_host_env = std::env::var("KAFKA_HOST"); let kafka_host = match kafka_host_env { Ok(val) => val, - Err(_) => "localhost:9093".to_owned() + Err(_) => "localhost:9093".to_owned(), }; let kafka_partition_manager_host_env = std::env::var("KAFKA_TOPIC_MANAGER_HOST"); let kafka_topic_manager_host = match kafka_partition_manager_host_env { Ok(val) => val, - Err(_) => "localhost:7243".to_owned() + Err(_) => "localhost:7243".to_owned(), }; HttpServer::new([0, 0, 0, 0], 4000, &kafka_host, &kafka_topic_manager_host) diff --git a/server/src/session.rs b/server/src/session.rs index dbc0f04..d8a95b1 100644 --- a/server/src/session.rs +++ b/server/src/session.rs @@ -4,8 +4,8 @@ use pong::event::event::{Event, EventReader, EventWriter}; use crate::hash::Hasher; use crate::kafka::{ - KafkaDefaultEventWriterImpl, KafkaSessionEventReaderImpl, - KafkaSessionEventWriterImpl, KafkaTopicManager, + KafkaDefaultEventWriterImpl, KafkaSessionEventReaderImpl, KafkaSessionEventWriterImpl, + KafkaTopicManager, }; use crate::player::Player; @@ -48,7 +48,10 @@ impl SessionManager { println!("Successfully created session: {:?}", session); let write_res = self.write_to_producer(session_created(session.clone(), player.clone())); if let Err(e) = write_res { - eprintln!("Failed to write session created event for {:?} to producer: {}", session, e); + eprintln!( + "Failed to write session created event for {:?} to producer: {}", + session, e + ); } self.sessions.push(session.clone()); Ok(session) @@ -86,9 +89,13 @@ impl SessionManager { session.clone() }; { - let write_res = self.write_to_producer(session_joined(updated_session.clone(), player.clone())); + let write_res = + self.write_to_producer(session_joined(updated_session.clone(), player.clone())); if let Err(e) = write_res { - eprintln!("Failed to write session joined event for {:?} to producer: {}", updated_session, e); + eprintln!( + "Failed to write session joined event for {:?} to producer: {}", + updated_session, e + ); } }; println!("sessions = {:?}", self.sessions); diff --git a/src/main.rs b/src/main.rs index e69de29..8b13789 100644 --- a/src/main.rs +++ b/src/main.rs @@ -0,0 +1 @@ +