mirror of
https://github.com/thilo-behnke/wasm-pong.git
synced 2026-07-11 02:29:25 +00:00
player bound collision tests
This commit is contained in:
@@ -93,10 +93,20 @@ pub mod collision {
|
||||
mapping: (String, String),
|
||||
callback: fn(&mut Box<dyn GameObject>, &mut Box<dyn GameObject>),
|
||||
) {
|
||||
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<dyn GameObject>, obj_b: &mut Box<dyn GameObject>) -> bool {
|
||||
pub fn handle(
|
||||
&self,
|
||||
obj_a: &mut Box<dyn GameObject>,
|
||||
obj_b: &mut Box<dyn GameObject>,
|
||||
) -> bool {
|
||||
let key = (obj_a.obj_type().to_string(), obj_b.obj_type().to_string());
|
||||
if !self.handlers.contains_key(&key) {
|
||||
return false;
|
||||
|
||||
@@ -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<dyn GameObject> {
|
||||
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<dyn GameObject> {
|
||||
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<Rc<RefCell<Box<dyn GameObject>>>> {
|
||||
let bounds: Vec<Box<dyn GameObject>> = vec![
|
||||
// top
|
||||
Box::new(DefaultGameObject::new(
|
||||
pub fn bounds(width: u16, height: u16) -> Vec<Bounds> {
|
||||
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<dyn GameObject>);
|
||||
|
||||
impl Bounds {
|
||||
pub fn inner(self) -> Box<dyn GameObject> {
|
||||
self.1
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,4 +2,5 @@ pub mod collision;
|
||||
pub mod game_field;
|
||||
pub mod game_object;
|
||||
pub mod geom;
|
||||
pub mod pong;
|
||||
pub mod utils;
|
||||
|
||||
53
pong/src/pong.rs
Normal file
53
pong/src/pong.rs
Normal file
@@ -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<dyn GameObject>,
|
||||
player: &mut Box<dyn GameObject>,
|
||||
) {
|
||||
// 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<dyn GameObject>,
|
||||
bound: &mut Box<dyn GameObject>,
|
||||
) {
|
||||
ball.vel_mut().reflect(&bound.orientation());
|
||||
}
|
||||
|
||||
pub fn handle_player_bound_collision(
|
||||
player: &mut Box<dyn GameObject>,
|
||||
bound: &mut Box<dyn GameObject>,
|
||||
) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -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<dyn GameObject>,
|
||||
) {
|
||||
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());
|
||||
|
||||
@@ -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![])]
|
||||
|
||||
@@ -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.);
|
||||
}
|
||||
}
|
||||
|
||||
75
pong/tests/pong_tests.rs
Normal file
75
pong/tests/pong_tests.rs
Normal file
@@ -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<dyn GameObject>,
|
||||
#[case] mut bounds: Box<dyn GameObject>,
|
||||
#[case] mut player_expected: Box<dyn GameObject>,
|
||||
#[case] mut bounds_expected: Box<dyn GameObject>
|
||||
) {
|
||||
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<dyn GameObject> {
|
||||
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<dyn GameObject> {
|
||||
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();
|
||||
}
|
||||
@@ -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::<Vec<GameObjectDTO>>();
|
||||
let mut objs = self
|
||||
.field
|
||||
.objs
|
||||
.borrow()
|
||||
.iter()
|
||||
.map(|o| GameObjectDTO::from(o))
|
||||
.collect::<Vec<GameObjectDTO>>();
|
||||
objs.as_ptr()
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user