From 967e13545d84757d2f3d53e5628c53acaa614e16 Mon Sep 17 00:00:00 2001 From: Thilo Behnke Date: Sun, 1 May 2022 18:08:50 +0200 Subject: [PATCH] player bound collision tests --- pong/src/collision.rs | 12 ++- pong/src/game_field.rs | 126 ++++++++++++++------------- pong/src/game_object.rs | 5 ++ pong/src/geom.rs | 2 + pong/src/lib.rs | 1 + pong/src/pong.rs | 53 +++++++++++ pong/tests/collision_handler_test.rs | 4 +- pong/tests/collision_tests.rs | 4 +- pong/tests/game_field_test.rs | 30 +++++-- pong/tests/pong_tests.rs | 75 ++++++++++++++++ src/lib.rs | 8 +- 11 files changed, 249 insertions(+), 71 deletions(-) create mode 100644 pong/src/pong.rs create mode 100644 pong/tests/pong_tests.rs diff --git a/pong/src/collision.rs b/pong/src/collision.rs index 7e50384..2650470 100644 --- a/pong/src/collision.rs +++ b/pong/src/collision.rs @@ -93,10 +93,20 @@ pub mod collision { mapping: (String, String), callback: fn(&mut Box, &mut Box), ) { + if self.handlers.contains_key(&mapping) { + panic!( + "Collision handler for mapping {:?} is already registered.", + mapping + ) + } self.handlers.insert(mapping, callback); } - pub fn handle(&self, obj_a: &mut Box, obj_b: &mut Box) -> bool { + pub fn handle( + &self, + obj_a: &mut Box, + obj_b: &mut Box, + ) -> bool { let key = (obj_a.obj_type().to_string(), obj_b.obj_type().to_string()); if !self.handlers.contains_key(&key) { return false; diff --git a/pong/src/game_field.rs b/pong/src/game_field.rs index 15f3119..c7dfdb3 100644 --- a/pong/src/game_field.rs +++ b/pong/src/game_field.rs @@ -1,4 +1,3 @@ -use std::borrow::{Borrow, BorrowMut}; use crate::collision::collision::{ Collision, CollisionDetector, CollisionHandler, CollisionRegistry, Collisions, }; @@ -6,7 +5,9 @@ use crate::game_object::components::{DefaultGeomComp, DefaultPhysicsComp}; use crate::game_object::game_object::{DefaultGameObject, GameObject}; use crate::geom::geom::Vector; use crate::geom::shape::{Shape, ShapeType}; +use crate::pong::pong_collisions::{handle_ball_bounds_collision, handle_player_ball_collision}; use crate::utils::utils::{Logger, NoopLogger}; +use std::borrow::{Borrow, BorrowMut}; use std::cell::{Cell, Ref, RefCell, RefMut}; use std::collections::HashMap; use std::ops::Deref; @@ -42,7 +43,10 @@ impl Field { logger, width, height, - objs: DefaultGameObject::bounds(width, height), + objs: DefaultGameObject::bounds(width, height) + .into_iter() + .map(|b| Rc::new(RefCell::new(b.inner()))) + .collect(), collisions: Box::new(Collisions::new(vec![])), collision_handler: CollisionHandler::new(), }; @@ -53,28 +57,17 @@ impl Field { field.collision_handler.register( (String::from("ball"), String::from("player")), - |ball, player| { - // reflect - ball.vel_mut().reflect(&player.orientation()); - // use vel of player obj - if *player.vel() != Vector::zero() { - let mut adjusted = player.vel().clone(); - adjusted.normalize(); - ball.vel_mut().add(&adjusted); - } - // move out of collision - let mut b_to_a = ball.pos().clone(); - b_to_a.sub(&player.pos()); - b_to_a.normalize(); - ball.pos_mut().add(&b_to_a); - }, + handle_player_ball_collision, ); field.collision_handler.register( (String::from("ball"), String::from("bound")), - |ball, bound| { - ball.vel_mut().reflect(&bound.orientation()); - }, + handle_ball_bounds_collision, + ); + + field.collision_handler.register( + (String::from("player"), String::from("bound")), + handle_ball_bounds_collision, ); return field; @@ -85,7 +78,10 @@ impl Field { logger: Box::new(NoopLogger {}), width, height, - objs: DefaultGameObject::bounds(width, height), + objs: DefaultGameObject::bounds(width, height) + .into_iter() + .map(|b| Rc::new(RefCell::new(b.inner()))) + .collect(), collisions: Box::new(Collisions::new(vec![])), collision_handler: CollisionHandler::new(), } @@ -193,18 +189,18 @@ impl Field { impl DefaultGameObject { pub fn player(id: u16, x: u16, y: u16, field: &Field) -> Box { Box::new(DefaultGameObject::new( - id, - "player".to_string(), - Box::new(DefaultGeomComp::new(Shape::rect( - Vector { - x: x as f64, - y: y as f64, - }, - Vector::new(0., 1.), - (field.width as f64) / 25., - (field.height as f64) / 5., - ))), - Box::new(DefaultPhysicsComp::new(Vector::zero(), true)), + id, + "player".to_string(), + Box::new(DefaultGeomComp::new(Shape::rect( + Vector { + x: x as f64, + y: y as f64, + }, + Vector::new(0., 1.), + (field.width as f64) / 25., + (field.height as f64) / 5., + ))), + Box::new(DefaultPhysicsComp::new(Vector::zero(), true)), )) } } @@ -212,26 +208,25 @@ impl DefaultGameObject { impl DefaultGameObject { pub fn ball(id: u16, x: u16, y: u16, field: &Field) -> Box { Box::new(DefaultGameObject::new( - id, - "ball".to_string(), - Box::new(DefaultGeomComp::new(Shape::circle( - Vector { - x: x as f64, - y: y as f64, - }, - Vector::zero(), - (field.width as f64) / 80., - ))), - Box::new(DefaultPhysicsComp::new(Vector::zero(), false)), + id, + "ball".to_string(), + Box::new(DefaultGeomComp::new(Shape::circle( + Vector { + x: x as f64, + y: y as f64, + }, + Vector::zero(), + (field.width as f64) / 80., + ))), + Box::new(DefaultPhysicsComp::new(Vector::zero(), false)), )) } } impl DefaultGameObject { - pub fn bounds(width: u16, height: u16) -> Vec>>> { - let bounds: Vec> = vec![ - // top - Box::new(DefaultGameObject::new( + pub fn bounds(width: u16, height: u16) -> Vec { + let bounds = vec![ + Bounds(Bound::BOTTOM, Box::new(DefaultGameObject::new( 90, "bound".to_string(), Box::new(DefaultGeomComp::new(Shape::rect( @@ -244,9 +239,8 @@ impl DefaultGameObject { 2., ))), Box::new(DefaultPhysicsComp::new_static()), - )), - // bottom - Box::new(DefaultGameObject::new( + ))), + Bounds(Bound::TOP, Box::new(DefaultGameObject::new( 91, "bound".to_string(), Box::new(DefaultGeomComp::new(Shape::rect( @@ -259,9 +253,8 @@ impl DefaultGameObject { 2., ))), Box::new(DefaultPhysicsComp::new_static()), - )), - // left - Box::new(DefaultGameObject::new( + ))), + Bounds(Bound::LEFT, Box::new(DefaultGameObject::new( 92, "bound".to_string(), Box::new(DefaultGeomComp::new(Shape::rect( @@ -274,9 +267,8 @@ impl DefaultGameObject { height as f64, ))), Box::new(DefaultPhysicsComp::new_static()), - )), - // right - Box::new(DefaultGameObject::new( + ))), + Bounds(Bound::RIGHT, Box::new(DefaultGameObject::new( 93, "bound".to_string(), Box::new(DefaultGeomComp::new(Shape::rect( @@ -289,8 +281,24 @@ impl DefaultGameObject { height as f64, ))), Box::new(DefaultPhysicsComp::new_static()), - )), + ))), ]; - bounds.into_iter().map(|o| Rc::new(RefCell::new(o))).collect() + bounds + } +} + +#[derive(Debug, PartialEq, Eq)] +pub enum Bound { + TOP, + RIGHT, + BOTTOM, + LEFT, +} + +pub struct Bounds(pub Bound, pub Box); + +impl Bounds { + pub fn inner(self) -> Box { + self.1 } } diff --git a/pong/src/game_object.rs b/pong/src/game_object.rs index 3b15a47..632568a 100644 --- a/pong/src/game_object.rs +++ b/pong/src/game_object.rs @@ -11,6 +11,7 @@ pub mod game_object { fn pos(&self) -> &Vector; fn pos_mut(&mut self) -> &mut Vector; fn orientation(&self) -> &Vector; + fn orientation_mut(&mut self) -> &mut Vector; fn update_pos(&mut self); fn bounding_box(&self) -> BoundingBox; fn vel(&self) -> &Vector; @@ -68,6 +69,10 @@ pub mod game_object { self.geom.orientation() } + fn orientation_mut(&mut self) -> &mut Vector { + self.geom.orientation_mut() + } + fn update_pos(&mut self) { let vel = self.vel().clone(); let center = self.geom.center_mut(); diff --git a/pong/src/geom.rs b/pong/src/geom.rs index 44804d8..6393101 100644 --- a/pong/src/geom.rs +++ b/pong/src/geom.rs @@ -108,10 +108,12 @@ pub mod geom { let mut orthogonal1 = onto.clone(); orthogonal1.orthogonal_clockwise(); if self.dot(&orthogonal1) < 0. { + // orthogonal1.normalize(); return orthogonal1; } let mut orthogonal2 = onto.clone(); orthogonal2.orthogonal_counter_clockwise(); + // orthogonal2.normalize(); return orthogonal2; } diff --git a/pong/src/lib.rs b/pong/src/lib.rs index 0faaf63..c1c5724 100644 --- a/pong/src/lib.rs +++ b/pong/src/lib.rs @@ -2,4 +2,5 @@ pub mod collision; pub mod game_field; pub mod game_object; pub mod geom; +pub mod pong; pub mod utils; diff --git a/pong/src/pong.rs b/pong/src/pong.rs new file mode 100644 index 0000000..9e291fa --- /dev/null +++ b/pong/src/pong.rs @@ -0,0 +1,53 @@ +pub mod pong_collisions { + use std::ops::Add; + use crate::game_object::game_object::GameObject; + use crate::geom::geom::Vector; + use crate::geom::shape::ShapeType; + + pub fn handle_player_ball_collision( + ball: &mut Box, + player: &mut Box, + ) { + // reflect + ball.vel_mut().reflect(&player.orientation()); + // use vel of player obj + if *player.vel() != Vector::zero() { + let mut adjusted = player.vel().clone(); + adjusted.normalize(); + ball.vel_mut().add(&adjusted); + } + // move out of collision + let mut b_to_a = ball.pos().clone(); + b_to_a.sub(&player.pos()); + b_to_a.normalize(); + ball.pos_mut().add(&b_to_a); + } + + pub fn handle_ball_bounds_collision( + ball: &mut Box, + bound: &mut Box, + ) { + ball.vel_mut().reflect(&bound.orientation()); + } + + pub fn handle_player_bound_collision( + player: &mut Box, + bound: &mut Box, + ) { + let shape = player.shape().clone(); + let player_orientation = player.orientation().clone(); + let height = match shape { + ShapeType::Rect(_, _, height) => height.clone(), + ShapeType::Circle(_, radius) => radius * 2., + }; + let mut perpendicular = player_orientation.get_opposing_orthogonal(bound.orientation()); + println!("{:?}", bound); + println!("{:?}", perpendicular); + perpendicular.x = 0.; + perpendicular.y *= height; + let mut new_pos = bound.pos().clone(); + new_pos.add(&perpendicular); + let player_pos = player.pos_mut(); + player_pos.y = new_pos.y; + } +} diff --git a/pong/tests/collision_handler_test.rs b/pong/tests/collision_handler_test.rs index f8f4339..ea452a8 100644 --- a/pong/tests/collision_handler_test.rs +++ b/pong/tests/collision_handler_test.rs @@ -1,10 +1,10 @@ -use std::borrow::{Borrow, BorrowMut}; use pong::collision::collision::CollisionHandler; use pong::game_object::components::{DefaultGeomComp, DefaultPhysicsComp}; use pong::game_object::game_object::{DefaultGameObject, GameObject}; use pong::geom::geom::Vector; use pong::geom::shape::Shape; use rstest::rstest; +use std::borrow::{Borrow, BorrowMut}; #[rstest] #[case( @@ -70,7 +70,7 @@ pub fn should_handle_collision( #[case] expected_b: Box, ) { let mut handler = CollisionHandler::new(); - handler.register((String::from("obj"), String::from("obj")), |a,b| {}); + handler.register((String::from("obj"), String::from("obj")), |a, b| {}); let res = handler.handle(&mut obj_a, &mut obj_b); assert_eq!(true, res) // assert_eq!(obj_a.pos(), expected_a.pos()); diff --git a/pong/tests/collision_tests.rs b/pong/tests/collision_tests.rs index ba87b6a..577533b 100644 --- a/pong/tests/collision_tests.rs +++ b/pong/tests/collision_tests.rs @@ -1,10 +1,10 @@ -use std::cell::{Ref, RefCell}; -use std::rc::Rc; use pong::collision::collision::{Collision, CollisionDetector}; use pong::game_object::game_object::GameObject; use pong::geom::geom::{BoundingBox, Vector}; use pong::geom::shape::ShapeType; use rstest::rstest; +use std::cell::{Ref, RefCell}; +use std::rc::Rc; #[rstest] #[case(vec![], vec![])] diff --git a/pong/tests/game_field_test.rs b/pong/tests/game_field_test.rs index 5cfb1df..d3328f1 100644 --- a/pong/tests/game_field_test.rs +++ b/pong/tests/game_field_test.rs @@ -1,8 +1,8 @@ #[cfg(test)] mod game_field_tests { + use pong::game_field::{Field, Input, InputType}; use std::borrow::Borrow; use std::cell::RefCell; - use pong::game_field::{Field, Input, InputType}; #[test] fn player_input_update_pos__up() { @@ -14,7 +14,13 @@ mod game_field_tests { obj_id: 1, }]; field.tick(inputs); - let player = RefCell::borrow(field.objs().iter().find(|o| RefCell::borrow(o).obj_type() == "player").unwrap()); + 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.); } @@ -29,7 +35,10 @@ mod game_field_tests { }]; field.tick(inputs); let objs = field.objs(); - let player = objs.iter().find(|o| RefCell::borrow(o).obj_type() == "player").unwrap(); + 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.); } @@ -44,8 +53,14 @@ mod game_field_tests { }]; field.tick(inputs); 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 - height as f64 / 5. / 2.); + let player = objs + .iter() + .find(|o| RefCell::borrow(o).obj_type() == "player") + .unwrap(); + assert_eq!( + RefCell::borrow(player).pos().y, + height as f64 - height as f64 / 5. / 2. + ); } #[test] @@ -59,7 +74,10 @@ mod game_field_tests { }]; field.tick(inputs); let objs = field.objs(); - let player = objs.iter().find(|o| RefCell::borrow(o).obj_type() == "player").unwrap(); + let player = objs + .iter() + .find(|o| RefCell::borrow(o).obj_type() == "player") + .unwrap(); assert_eq!(RefCell::borrow(player).pos().y, height as f64 / 5. / 2.); } } diff --git a/pong/tests/pong_tests.rs b/pong/tests/pong_tests.rs new file mode 100644 index 0000000..c65a59d --- /dev/null +++ b/pong/tests/pong_tests.rs @@ -0,0 +1,75 @@ +use rstest::rstest; +use pong::game_field::{Bound, Field}; +use pong::game_object::game_object::{DefaultGameObject, GameObject}; +use pong::geom::geom::Vector; +use pong::pong::pong_collisions::handle_player_bound_collision; +use pong::utils::utils::NoopLogger; + +#[rstest] +#[case( + // given + create_player(1, 10, 0, Vector::new(0., -1.)), + get_bound(Bound::BOTTOM), + // expected + create_player(1, 10, 120, Vector::new(0., -1.)), + get_bound(Bound::BOTTOM) +)] +#[case( + // given + create_player(1, 10, 119, Vector::new(0., -1.)), + get_bound(Bound::BOTTOM), + // expected + create_player(1, 10, 120, Vector::new(0., -1.)), + get_bound(Bound::BOTTOM) +)] +#[case( + // given + create_player(1, 10, 121, Vector::new(0., -1.)), + get_bound(Bound::BOTTOM), + // expected + create_player(1, 10, 120, Vector::new(0., -1.)), + get_bound(Bound::BOTTOM) +)] +#[case( + // given + create_player(1, 10, 600, Vector::new(0., 1.)), + get_bound(Bound::TOP), + // expected + create_player(1, 10, 480, 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, 480, Vector::new(0., 1.)), + get_bound(Bound::TOP) +)] +pub fn should_correctly_handle_player_bounds_collision( + #[case] mut player: Box, + #[case] mut bounds: Box, + #[case] mut player_expected: Box, + #[case] mut bounds_expected: Box +) { + handle_player_bound_collision(&mut player, &mut bounds); + assert_eq!(player_expected.pos(), player.pos()); + assert_eq!(bounds_expected.pos(), bounds.pos()); +} + +fn create_player(id: u16, x: u16, y: u16, orientation: Vector) -> Box { + let field = Field::new(Box::new(NoopLogger{})); + 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; + player +} + +fn get_bound(bound: Bound) -> Box { + let field = Field::new(Box::new(NoopLogger{})); + let bounds = DefaultGameObject::bounds(field.width, field.height); + return bounds.into_iter().find(|b| { + b.0 == bound + }).unwrap().inner(); +} diff --git a/src/lib.rs b/src/lib.rs index 2408c91..8d7f8ae 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -121,7 +121,13 @@ impl FieldWrapper { } pub fn objects(&self) -> *const GameObjectDTO { - let mut objs = self.field.objs.borrow().iter().map(|o| GameObjectDTO::from(o)).collect::>(); + let mut objs = self + .field + .objs + .borrow() + .iter() + .map(|o| GameObjectDTO::from(o)) + .collect::>(); objs.as_ptr() }