dev-ops/fix-and-cleanup-unit-tests

This commit is contained in:
Thilo Behnke
2022-06-16 15:55:57 +02:00
parent 9c3fc1f36b
commit 5758f4fc24
19 changed files with 576 additions and 519 deletions

View File

@@ -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};

View File

@@ -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<Collision>,
}
impl Collisions {
pub fn new(collisions: Vec<Collision>) -> 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<Rc<RefCell<Box<dyn GameObject>>>>,
#[case] expected_collisions: Vec<Collision>,
) {
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::<Vec<&Collision>>()
);
}
}
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<RefCell<Box<dyn GameObject>>>, &Rc<RefCell<Box<dyn GameObject>>>),
>,
}
type CollisionCallback = fn(&Rc<RefCell<Box<dyn GameObject>>>, &Rc<RefCell<Box<dyn GameObject>>>);
impl CollisionHandlerRegistry {
pub fn new() -> CollisionHandlerRegistry {
CollisionHandlerRegistry {
handlers: HashMap::new(),
impl MockGameObject {
pub fn new(
id: u16,
obj_type: &str,
bounding_box: BoundingBox,
) -> Rc<RefCell<Box<dyn GameObject>>> {
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<RefCell<Box<dyn GameObject>>>,
&Rc<RefCell<Box<dyn GameObject>>>,
),
) -> 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<dyn Logger>,
@@ -223,4 +295,171 @@ pub mod collision {
// obj_a.pos_mut().add(&b_to_a);
// }
}
pub struct CollisionHandlerRegistry {
handlers: HashMap<
(String, String),
fn(&Rc<RefCell<Box<dyn GameObject>>>, &Rc<RefCell<Box<dyn GameObject>>>),
>,
}
type CollisionCallback =
fn(&Rc<RefCell<Box<dyn GameObject>>>, &Rc<RefCell<Box<dyn GameObject>>>);
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<RefCell<Box<dyn GameObject>>>,
&Rc<RefCell<Box<dyn GameObject>>>,
),
) -> 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<RefCell<Box<dyn GameObject>>>,
#[case] obj_b: Rc<RefCell<Box<dyn GameObject>>>,
) {
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<RefCell<Box<dyn GameObject>>> {
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<Collision>,
}
impl Collisions {
pub fn new(collisions: Vec<Collision>) -> 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);
}

View File

@@ -37,7 +37,7 @@ pub mod event {
pub struct NoopEventWriterImpl {}
impl EventWriterImpl for NoopEventWriterImpl {
fn write(&mut self, _event: Event) -> Result<(), String> {
todo!()
Ok(())
}
}

View File

@@ -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.);
}
}

View File

@@ -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;

View File

@@ -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 {

View File

@@ -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<RefCell<Box<dyn GameObject>>>,
#[case] bounds: Rc<RefCell<Box<dyn GameObject>>>,
#[case] player_expected: Rc<RefCell<Box<dyn GameObject>>>,
#[case] bounds_expected: Rc<RefCell<Box<dyn GameObject>>>,
) {
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<RefCell<Box<dyn GameObject>>> {
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<RefCell<Box<dyn GameObject>>> {
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 {

View File

@@ -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);
}

View File

@@ -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<RefCell<Box<dyn GameObject>>>,
#[case] obj_b: Rc<RefCell<Box<dyn GameObject>>>
) {
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<RefCell<Box<dyn GameObject>>> {
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)),
))))
}

View File

@@ -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<Rc<RefCell<Box<dyn GameObject>>>>,
#[case] expected_collisions: Vec<Collision>,
) {
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::<Vec<&Collision>>()
);
}
#[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<RefCell<Box<dyn GameObject>>> {
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!()
}
}

View File

@@ -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.);
}
}

View File

@@ -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);
}

View File

@@ -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<RefCell<Box<dyn GameObject>>>,
#[case] bounds: Rc<RefCell<Box<dyn GameObject>>>,
#[case] player_expected: Rc<RefCell<Box<dyn GameObject>>>,
#[case] bounds_expected: Rc<RefCell<Box<dyn GameObject>>>,
) {
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<RefCell<Box<dyn GameObject>>> {
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<RefCell<Box<dyn GameObject>>> {
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(),
));
}

View File

@@ -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<Mutex<SessionManager>>,
}
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,

View File

@@ -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;

View File

@@ -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)

View File

@@ -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);

View File

@@ -0,0 +1 @@