using refcells?

This commit is contained in:
Thilo Behnke
2022-04-25 23:57:40 +02:00
parent 61bf393ea4
commit 2aff2713d3
13 changed files with 342 additions and 362 deletions

View File

@@ -1,9 +1,10 @@
pub mod collision {
use std::alloc::handle_alloc_error;
use std::collections::HashMap;
use std::fmt::Debug;
use crate::game_object::game_object::GameObject;
use crate::geom::geom::Vector;
use std::alloc::handle_alloc_error;
use std::cell::{Ref, RefCell};
use std::collections::HashMap;
use std::fmt::Debug;
pub struct CollisionDetector {}
@@ -12,18 +13,21 @@ pub mod collision {
CollisionDetector {}
}
pub fn detect_collisions(&self, objs: &Vec<&Box<dyn GameObject>>) -> Box<dyn CollisionRegistry> {
pub fn detect_collisions(
&self,
objs: Vec<&RefCell<Box<dyn GameObject>>>,
) -> Box<dyn CollisionRegistry> {
if objs.is_empty() {
return Box::new(Collisions::new(vec![]));
}
let mut collisions: Vec<Collision> = vec![];
let mut i = 0;
loop {
let obj = objs[i];
let obj = objs[i].borrow();
i += 1;
let rest = &objs[i..];
for other in rest.iter() {
for other in rest.iter().map(|o| o.borrow()) {
let has_collision = obj.bounding_box().overlaps(&other.bounding_box());
if !has_collision {
continue;
@@ -39,7 +43,7 @@ pub mod collision {
}
}
pub trait CollisionRegistry : Debug {
pub trait CollisionRegistry: Debug {
fn get_collisions(&self) -> Vec<&Collision>;
fn get_collisions_by_id(&self, id: u16) -> Vec<&Collision>;
}
@@ -70,18 +74,23 @@ pub mod collision {
#[derive(Debug, Eq, PartialEq)]
pub struct Collision(pub u16, pub u16);
#[derive(Clone)]
pub struct CollisionHandler {
handlers: HashMap<(String, String), fn(&mut Box<dyn GameObject>, &mut Box<dyn GameObject>)>
handlers: HashMap<(String, String), fn(&mut Box<dyn GameObject>, &mut Box<dyn GameObject>)>,
}
impl CollisionHandler {
pub fn new() -> CollisionHandler {
CollisionHandler {
handlers: HashMap::new()
handlers: HashMap::new(),
}
}
pub fn register(&mut self, mapping: (String, String), callback: fn(&mut Box<dyn GameObject>, &mut Box<dyn GameObject>)) {
pub fn register(
&mut self,
mapping: (String, String),
callback: fn(&mut Box<dyn GameObject>, &mut Box<dyn GameObject>),
) {
self.handlers.insert(mapping, callback);
}

View File

@@ -1,9 +1,15 @@
use crate::collision::collision::{Collision, CollisionDetector, CollisionHandler, CollisionRegistry, Collisions};
use std::borrow::{Borrow, BorrowMut};
use crate::collision::collision::{
Collision, CollisionDetector, CollisionHandler, CollisionRegistry, Collisions,
};
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::utils::utils::{Logger, NoopLogger};
use std::cell::{Cell, RefCell, RefMut};
use std::collections::HashMap;
use std::ops::Deref;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum InputType {
@@ -21,11 +27,9 @@ pub struct Field {
pub logger: Box<dyn Logger>,
pub width: u16,
pub height: u16,
pub players: Vec<Player>,
pub balls: Vec<Ball>,
pub bounds: Bounds,
pub objs: HashMap<u16, RefCell<Box<dyn GameObject>>>,
pub collisions: Box<dyn CollisionRegistry>,
collision_handler: CollisionHandler
collision_handler: CollisionHandler,
}
impl Field {
@@ -37,36 +41,40 @@ impl Field {
logger,
width,
height,
players: vec![],
balls: vec![],
bounds: Bounds::new(width, height),
objs: DefaultGameObject::bounds(width, height),
collisions: Box::new(Collisions::new(vec![])),
collision_handler: CollisionHandler::new()
collision_handler: CollisionHandler::new(),
};
field.add_player(0, 0 + width / 20, height / 2);
field.add_player(1, width - width / 20, height / 2);
field.add_ball(2, width / 2, height / 2);
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);
});
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);
},
);
field.collision_handler.register((String::from("ball"), String::from("bound")), |ball, bound| {
ball.vel_mut().reflect(&bound.orientation());
});
field.collision_handler.register(
(String::from("ball"), String::from("bound")),
|ball, bound| {
ball.vel_mut().reflect(&bound.orientation());
},
);
return field;
}
@@ -76,64 +84,77 @@ impl Field {
logger: Box::new(NoopLogger {}),
width,
height,
players: vec![],
balls: vec![],
bounds: Bounds::new(width, height),
objs: DefaultGameObject::bounds(width, height),
collisions: Box::new(Collisions::new(vec![])),
collision_handler: CollisionHandler::new()
collision_handler: CollisionHandler::new(),
}
}
pub fn add_player(&mut self, id: u16, x: u16, y: u16) {
self.players.push(Player::new(id, x, y, &self));
let player = DefaultGameObject::player(id, x, y, &self);
self.objs.insert(player.id(), RefCell::new(player));
}
pub fn add_ball(&mut self, id: u16, x: u16, y: u16) {
let ball = Ball::new(id, x, y, &self);
self.balls.push(ball);
let ball = DefaultGameObject::ball(id, x, y, &self);
self.objs.insert(ball.id(), RefCell::new(ball));
}
pub fn tick(&mut self, inputs: Vec<Input>) {
for ball in self.balls.iter_mut() {
if *ball.obj.vel() == Vector::zero() {
ball.obj.vel_mut().add(&Vector::new(-2.0, 0.))
{
for (_, obj) in self.objs.iter() {
let mut obj_mut = obj.borrow_mut();
if obj_mut.obj_type() != "ball" {
continue;
}
if *obj_mut.vel() == Vector::zero() {
obj_mut.vel_mut().add(&Vector::new(-2.0, 0.))
}
}
}
for player in self.players.iter_mut() {
let input_opt = inputs.iter().find(|input| player.obj.id() == input.obj_id);
if let None = input_opt {
player.obj.vel_mut().y = 0.;
continue;
{
for (_, obj) in self.objs.iter() {
let mut obj_mut = obj.borrow_mut();
if obj_mut.obj_type() != "player" {
continue;
}
let input_opt = inputs.iter().find(|i| i.obj_id == obj_mut.id());
if let None = input_opt {
obj_mut.vel_mut().y = 0.;
continue;
}
let input = input_opt.unwrap();
match input.input {
InputType::UP => {
let updated_vel_y = (obj_mut.vel().y + 1.).min(5.);
obj_mut.vel_mut().y = updated_vel_y;
}
InputType::DOWN => {
let updated_vel_y = (obj_mut.vel().y - 1.).max(-5.);
obj_mut.vel_mut().y = updated_vel_y;
}
};
}
let input = input_opt.unwrap();
match input.input {
InputType::UP => {
let updated_vel_y = (player.obj.vel().y + 1.).min(5.);
player.obj.vel_mut().y = updated_vel_y;
}
InputType::DOWN => {
let updated_vel_y = (player.obj.vel().y - 1.).max(-5.);
player.obj.vel_mut().y = updated_vel_y;
}
};
}
for player in self.players.iter_mut() {
player.obj.update_pos()
}
for ball in self.balls.iter_mut() {
ball.obj.update_pos()
{
for (_, obj) in self.objs.iter() {
let mut obj_mut = obj.borrow_mut();
obj_mut.update_pos();
}
}
let collisions = self.get_collisions();
let collision_handler = self.collision_handler.clone();
for collision in collisions.get_collisions().iter() {
// self.handle_collision(c);
let objs_mut = self.objs_mut();
let obj_a = objs_mut.iter().find(|o| o.id() == collision.0).unwrap();
let obj_b = objs_mut.iter().find(|o| o.id() == collision.1).unwrap();
self.collision_handler.handle(*obj_a, *obj_b)
// TODO: This is fine because an obj will not collide with itself - better abstraction?
// let idx
// let (obj_a, obj_b) = self.objs.split_at_mut();
// let obj_a = self.get_obj(collision.0, self.objs.borrow_mut());
// let obj_b = self.get_obj(collision.1, self.objs.borrow_mut());
// collision_handler.handle(obj_a, obj_b)
}
//
// for ball in self.balls.iter_mut() {
@@ -160,165 +181,117 @@ impl Field {
}
fn get_collisions(&self) -> Box<dyn CollisionRegistry> {
let objs = self.objs();
let objs = self.objs.iter().map(|(_, o)| o).collect();
let collision_detector = CollisionDetector::new();
collision_detector.detect_collisions(&objs)
collision_detector.detect_collisions(objs)
}
fn handle_collision(&mut self, collision: &Collision) {
let objs_mut = self.objs_mut();
let obj_a = objs_mut.iter().find(|o| o.id() == collision.0).unwrap();
let obj_b = objs_mut.iter().find(|o| o.id() == collision.1).unwrap();
self.collision_handler.handle(*obj_a, *obj_b)
}
pub fn players(&self) -> Vec<&Player> {
self.players.iter().collect()
}
pub fn balls(&self) -> Vec<&Ball> {
self.balls.iter().collect()
}
fn objs(&self) -> Vec<&Box<dyn GameObject>> {
let mut objs: Vec<&Box<dyn GameObject>> = vec![];
objs.extend(
self.players
.iter()
.map(|p| &p.obj)
.collect::<Vec<&Box<dyn GameObject>>>(),
);
objs.extend(
self.balls
.iter()
.map(|b| &b.obj)
.collect::<Vec<&Box<dyn GameObject>>>(),
);
objs.extend(
self.bounds.objs
.iter()
.collect::<Vec<&Box<dyn GameObject>>>()
);
objs
}
fn objs_mut(&mut self) -> Vec<&mut Box<dyn GameObject>> {
let mut objs: Vec<&mut Box<dyn GameObject>> = vec![];
objs.extend(
self.players
.iter_mut()
.map(|p| &mut p.obj)
.collect::<Vec<&mut Box<dyn GameObject>>>(),
);
objs.extend(
self.balls
.iter_mut()
.map(|b| &mut b.obj)
.collect::<Vec<&mut Box<dyn GameObject>>>(),
);
objs.extend(
self.bounds.objs
.iter_mut()
.collect::<Vec<&mut Box<dyn GameObject>>>()
);
objs
fn get_obj<'a>(&self, id: u16, mut objs: &'a RefMut<Vec<Box<dyn GameObject>>>) -> &'a mut Box<dyn GameObject> {
objs.iter_mut().find(|o| o.id() == id).unwrap()
}
}
// #[derive(Clone, Debug, PartialEq)]
#[derive(Debug)]
pub struct Player {
pub obj: Box<dyn GameObject>,
}
impl Player {
pub fn new(id: u16, x: u16, y: u16, field: &Field) -> Player {
Player {
obj: Box::new(DefaultGameObject::new(
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
))
))
}
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)),
))
}
}
// #[derive(Clone, Debug, PartialEq)]
#[derive(Debug)]
pub struct Ball {
pub obj: Box<dyn GameObject>,
}
impl Ball {
pub fn new(id: u16, x: u16, y: u16, field: &Field) -> Ball {
Ball {
obj: Box::new(DefaultGameObject::new(
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(
Box::new(DefaultGeomComp::new(Shape::circle(
Vector {
x: x as f64,
y: y as f64,
},
Vector::zero(),
false
))
))
}
(field.width as f64) / 80.,
))),
Box::new(DefaultPhysicsComp::new(Vector::zero(), false)),
))
}
}
#[derive(Debug)]
pub struct Bounds {
pub objs: Vec<Box<dyn GameObject>>,
}
impl Bounds {
pub fn new(width: u16, height: u16) -> Bounds {
Bounds {
objs: vec![
// top
Box::new(DefaultGameObject::new(
90,
"bound".to_string(),
Box::new(DefaultGeomComp::new(
Shape::rect(Vector {x: (width / 2) as f64, y: 0 as f64}, Vector::new(1., 0.), width as f64, 2.)
)),
Box::new(DefaultPhysicsComp::new_static())
)),
// bottom
Box::new(DefaultGameObject::new(
91,
"bound".to_string(),
Box::new(DefaultGeomComp::new(
Shape::rect(Vector {x: (width / 2) as f64, y: height as f64}, Vector::new(-1., 0.), width as f64, 2.)
)),
Box::new(DefaultPhysicsComp::new_static())
)),
// left
Box::new(DefaultGameObject::new(
92,
"bound".to_string(),
Box::new(DefaultGeomComp::new(
Shape::rect(Vector {x: 0 as f64, y: (height / 2) as f64}, Vector::new(0., 1.), 2., height as f64)
)),
Box::new(DefaultPhysicsComp::new_static())
)),
// right
Box::new(DefaultGameObject::new(
93,
"bound".to_string(),
Box::new(DefaultGeomComp::new(
Shape::rect(Vector {x: width as f64, y: (height / 2) as f64}, Vector::new(0., -1.), 2., height as f64)
)),
Box::new(DefaultPhysicsComp::new_static())
))
],
}
impl DefaultGameObject {
pub fn bounds(width: u16, height: u16) -> HashMap<u16, RefCell<Box<dyn GameObject>>> {
let bounds = vec![
// top
Box::new(DefaultGameObject::new(
90,
"bound".to_string(),
Box::new(DefaultGeomComp::new(Shape::rect(
Vector {
x: (width / 2) as f64,
y: 0 as f64,
},
Vector::new(1., 0.),
width as f64,
2.,
))),
Box::new(DefaultPhysicsComp::new_static()),
)),
// bottom
Box::new(DefaultGameObject::new(
91,
"bound".to_string(),
Box::new(DefaultGeomComp::new(Shape::rect(
Vector {
x: (width / 2) as f64,
y: height as f64,
},
Vector::new(-1., 0.),
width as f64,
2.,
))),
Box::new(DefaultPhysicsComp::new_static()),
)),
// left
Box::new(DefaultGameObject::new(
92,
"bound".to_string(),
Box::new(DefaultGeomComp::new(Shape::rect(
Vector {
x: 0 as f64,
y: (height / 2) as f64,
},
Vector::new(0., 1.),
2.,
height as f64,
))),
Box::new(DefaultPhysicsComp::new_static()),
)),
// right
Box::new(DefaultGameObject::new(
93,
"bound".to_string(),
Box::new(DefaultGeomComp::new(Shape::rect(
Vector {
x: width as f64,
y: (height / 2) as f64,
},
Vector::new(0., -1.),
2.,
height as f64,
))),
Box::new(DefaultPhysicsComp::new_static()),
)),
];
bounds.iter().map(|o| (o.id, RefCell::new(o))).collect()
}
}

View File

@@ -1,10 +1,10 @@
pub mod game_object {
use std::fmt::Debug;
use crate::game_object::components::{GeomComp, PhysicsComp};
use crate::geom::geom::{BoundingBox, Vector};
use crate::geom::shape::{Shape, ShapeType};
use std::fmt::Debug;
pub trait GameObject : Debug {
pub trait GameObject: Debug {
fn id(&self) -> u16;
fn obj_type(&self) -> &str;
fn shape(&self) -> &ShapeType;
@@ -24,12 +24,22 @@ pub mod game_object {
pub id: u16,
pub obj_type: String,
geom: Box<dyn GeomComp>,
physics: Box<dyn PhysicsComp>
physics: Box<dyn PhysicsComp>,
}
impl DefaultGameObject {
pub fn new(id: u16, obj_type: String, geom: Box<dyn GeomComp>, physics: Box<dyn PhysicsComp>) -> DefaultGameObject {
DefaultGameObject {id, obj_type, geom, physics}
pub fn new(
id: u16,
obj_type: String,
geom: Box<dyn GeomComp>,
physics: Box<dyn PhysicsComp>,
) -> DefaultGameObject {
DefaultGameObject {
id,
obj_type,
geom,
physics,
}
}
}
@@ -92,11 +102,14 @@ pub mod game_object {
}
pub mod components {
use std::fmt::Debug;
use crate::geom::geom::{BoundingBox, Vector};
use crate::geom::shape::{get_bounding_box, get_center, get_center_mut, get_orientation, get_orientation_mut, Shape, ShapeType};
use crate::geom::shape::{
get_bounding_box, get_center, get_center_mut, get_orientation, get_orientation_mut, Shape,
ShapeType,
};
use std::fmt::Debug;
pub trait GeomComp : Debug {
pub trait GeomComp: Debug {
fn shape(&self) -> &ShapeType;
fn orientation(&self) -> &Vector;
fn orientation_mut(&mut self) -> &mut Vector;
@@ -107,12 +120,12 @@ pub mod components {
#[derive(Debug)]
pub struct DefaultGeomComp {
shape: ShapeType
shape: ShapeType,
}
impl DefaultGeomComp {
pub fn new(shape: ShapeType) -> DefaultGeomComp {
DefaultGeomComp {shape}
DefaultGeomComp { shape }
}
}
@@ -142,7 +155,7 @@ pub mod components {
}
}
pub trait PhysicsComp : Debug {
pub trait PhysicsComp: Debug {
fn vel(&self) -> &Vector;
fn vel_mut(&mut self) -> &mut Vector;
fn is_static(&self) -> bool;
@@ -151,18 +164,15 @@ pub mod components {
#[derive(Debug)]
pub struct DefaultPhysicsComp {
vel: Vector,
is_static: bool
is_static: bool,
}
impl DefaultPhysicsComp {
pub fn new(vel: Vector, is_static: bool) -> DefaultPhysicsComp {
DefaultPhysicsComp {vel, is_static}
DefaultPhysicsComp { vel, is_static }
}
pub fn new_static() -> DefaultPhysicsComp {
DefaultPhysicsComp::new(
Vector::zero(),
true
)
DefaultPhysicsComp::new(Vector::zero(), true)
}
}
impl PhysicsComp for DefaultPhysicsComp {

View File

@@ -17,9 +17,7 @@ pub mod geom {
}
pub fn new(x: f64, y: f64) -> Vector {
Vector {
x, y
}
Vector { x, y }
}
pub fn normalize(&mut self) {
@@ -68,7 +66,7 @@ pub mod geom {
}
pub fn dot(&self, other: &Vector) -> f64 {
return self.x * other.x + self.y * other.y
return self.x * other.x + self.y * other.y;
}
pub fn angle(&self, other: &Vector) -> f64 {
@@ -130,8 +128,8 @@ pub mod geom {
impl PartialEq for Vector {
fn eq(&self, other: &Self) -> bool {
(self.x * 1000.).round() == (other.x * 1000.).round() &&
(self.y * 1000.).round() == (other.y * 1000.).round()
(self.x * 1000.).round() == (other.x * 1000.).round()
&& (self.y * 1000.).round() == (other.y * 1000.).round()
}
}
@@ -208,13 +206,9 @@ pub mod geom {
impl Range {
pub fn new(a: f64, b: f64) -> Range {
if a <= b {
return Range {
min: a, max: b
}
}
return Range {
min: b, max: a
return Range { min: a, max: b };
}
return Range { min: b, max: a };
}
pub fn overlaps(&self, other: &Range) -> bool {
@@ -240,8 +234,8 @@ pub mod geom {
}
pub mod shape {
use std::fmt::Debug;
use crate::geom::geom::{BoundingBox, Vector};
use std::fmt::Debug;
#[derive(Clone, Debug, PartialEq)]
pub enum ShapeType {
@@ -252,20 +246,29 @@ pub mod shape {
#[derive(Clone, Debug, PartialEq)]
pub struct Shape {
center: Vector,
orientation: Vector
orientation: Vector,
}
impl Shape {
pub fn rect(center: Vector, orientation: Vector, width: f64, height: f64) -> ShapeType {
ShapeType::Rect(Shape {
center, orientation
}, width, height)
ShapeType::Rect(
Shape {
center,
orientation,
},
width,
height,
)
}
pub fn circle(center: Vector, orientation: Vector, radius: f64) -> ShapeType {
ShapeType::Circle(Shape {
center, orientation
}, radius)
ShapeType::Circle(
Shape {
center,
orientation,
},
radius,
)
}
fn center(&self) -> &Vector {
@@ -288,35 +291,37 @@ pub mod shape {
pub fn get_center(shape: &ShapeType) -> &Vector {
match shape {
ShapeType::Rect(ref s, _, _) => &s.center,
ShapeType::Circle(ref s, _) => &s.center
ShapeType::Circle(ref s, _) => &s.center,
}
}
pub fn get_center_mut(shape: &mut ShapeType) -> &mut Vector {
match shape {
ShapeType::Rect(ref mut s, _, _) => &mut s.center,
ShapeType::Circle(ref mut s, _) => &mut s.center
ShapeType::Circle(ref mut s, _) => &mut s.center,
}
}
pub fn get_orientation(shape: &ShapeType) -> &Vector {
match shape {
ShapeType::Rect(s, _, _) => &s.orientation,
ShapeType::Circle(s, _) => &s.orientation
ShapeType::Circle(s, _) => &s.orientation,
}
}
pub fn get_orientation_mut(shape: &mut ShapeType) -> &mut Vector {
match shape {
ShapeType::Rect(ref mut s, _, _) => &mut s.orientation,
ShapeType::Circle(ref mut s, _) => &mut s.orientation
ShapeType::Circle(ref mut s, _) => &mut s.orientation,
}
}
pub fn get_bounding_box(shape: &ShapeType) -> BoundingBox {
match shape {
ShapeType::Rect(s, width, height) => BoundingBox::create(&s.center, *width, *height),
ShapeType::Circle(s, radius) => BoundingBox::create(&s.center, *radius * 2., *radius * 2.)
ShapeType::Circle(s, radius) => {
BoundingBox::create(&s.center, *radius * 2., *radius * 2.)
}
}
}
}

View File

@@ -1,5 +1,5 @@
pub mod collision;
pub mod game_field;
pub mod game_object;
pub mod geom;
pub mod game_field;
pub mod utils;

View File

@@ -1,4 +1,3 @@
pub mod utils {
pub trait Logger {
fn log(&self, msg: &str);
@@ -6,7 +5,6 @@ pub mod utils {
pub struct NoopLogger {}
impl Logger for NoopLogger {
fn log(&self, msg: &str) {
}
fn log(&self, msg: &str) {}
}
}

View File

@@ -1,5 +1,5 @@
use rstest::rstest;
use pong::geom::geom::{BoundingBox, Vector};
use rstest::rstest;
#[rstest]
#[case(BoundingBox::create(&Vector::new(10., 10.), 5., 5.), Vector::new(10., 10.), true)]

View File

@@ -1,9 +1,9 @@
use rstest::rstest;
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;
#[rstest]
#[case(
@@ -76,15 +76,21 @@ pub fn should_handle_collision(
assert_eq!(obj_b.vel(), expected_b.vel());
}
fn create_game_obj(id: u16, vel: Vector, orientation: Vector, is_static: bool) -> Box<dyn GameObject> {
fn create_game_obj(
id: u16,
vel: Vector,
orientation: Vector,
is_static: bool,
) -> Box<dyn GameObject> {
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
))
"obj".to_string(),
Box::new(DefaultGeomComp::new(Shape::rect(
Vector::zero(),
orientation,
20.,
20.,
))),
Box::new(DefaultPhysicsComp::new(vel, is_static)),
))
}

View File

@@ -1,46 +1,46 @@
use rstest::rstest;
use std::cell::{Ref, RefCell};
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;
#[rstest]
#[case(vec![], vec![])]
#[case(RefCell::new(vec![]), vec![])]
#[case(
vec![
RefCell::new(vec![
MockGameObject::new(1, BoundingBox::create(&Vector{x: 50., y: 50.}, 20., 20.)),
MockGameObject::new(2, BoundingBox::create(&Vector{x: 50., y: 50.}, 20., 20.))
],
]),
vec![Collision(1, 2)]
)]
#[case(
vec![
RefCell::new(vec![
MockGameObject::new(1, BoundingBox::create(&Vector{x: 60., y: 65.}, 20., 20.)),
MockGameObject::new(2, BoundingBox::create(&Vector{x: 50., y: 50.}, 20., 20.)),
],
]),
vec![Collision(1, 2)]
)]
#[case(
vec![
RefCell::new(vec![
MockGameObject::new(1, BoundingBox::create(&Vector{x: 50., y: 50.}, 20., 20.)),
MockGameObject::new(2, BoundingBox::create(&Vector{x: 80., y: 80.}, 20., 20.)),
],
]),
vec![]
)]
#[case(
vec![
RefCell::new(vec![
MockGameObject::new(1, BoundingBox::create(&Vector{x: 50., y: 50.}, 50., 50.)),
MockGameObject::new(2, BoundingBox::create(&Vector{x: 500., y: 50.}, 50., 50.)),
],
]),
vec![]
)]
pub fn should_detect_collisions(
#[case] objs: Vec<Box<dyn GameObject>>,
#[case] objs: RefCell<Vec<Box<dyn GameObject>>>,
#[case] expected_collisions: Vec<Collision>,
) {
let detector = CollisionDetector::new();
let res = detector.detect_collisions(&objs.iter().collect());
let res = detector.detect_collisions(objs.borrow());
assert_eq!(
res.get_collisions(),
expected_collisions.iter().collect::<Vec<&Collision>>()
@@ -51,16 +51,16 @@ pub fn should_detect_collisions(
pub struct MockGameObject {
id: u16,
bounding_box: BoundingBox,
zero_vec: Vector
zero_vec: Vector,
}
impl MockGameObject {
pub fn new(id: u16, bounding_box: BoundingBox) -> Box<dyn GameObject> {
Box::new(
MockGameObject {
id, bounding_box, zero_vec: Vector::zero()
}
)
Box::new(MockGameObject {
id,
bounding_box,
zero_vec: Vector::zero(),
})
}
}
@@ -73,7 +73,6 @@ impl GameObject for MockGameObject {
todo!()
}
fn shape(&self) -> &ShapeType {
todo!()
}

View File

@@ -12,9 +12,9 @@ mod game_field_tests {
obj_id: 1,
}];
field.tick(inputs);
let players = field.players();
let player = players.first().unwrap();
assert_eq!(player.obj.pos().y, height as f64 / 2. + 1.);
let objs = field.objs.borrow();
let player = objs.iter().find(|o| o.obj_type() == "player").unwrap();
assert_eq!(player.pos().y, height as f64 / 2. + 1.);
}
#[test]
@@ -27,9 +27,9 @@ mod game_field_tests {
obj_id: 1,
}];
field.tick(inputs);
let players = field.players();
let player = players.first().unwrap();
assert_eq!(player.obj.pos().y, height as f64 / 2. - 1.);
let objs = field.objs.borrow();
let player = objs.iter().find(|o| o.obj_type() == "player").unwrap();
assert_eq!(player.pos().y, height as f64 / 2. - 1.);
}
#[test]
@@ -42,9 +42,9 @@ mod game_field_tests {
obj_id: 1,
}];
field.tick(inputs);
let players = field.players();
let player = players.first().unwrap();
assert_eq!(player.obj.pos().y, height as f64 - height as f64 / 5. / 2.);
let objs = field.objs.borrow();
let player = objs.iter().find(|o| o.obj_type() == "player").unwrap();
assert_eq!(player.pos().y, height as f64 - height as f64 / 5. / 2.);
}
#[test]
@@ -57,8 +57,8 @@ mod game_field_tests {
obj_id: 1,
}];
field.tick(inputs);
let players = field.players();
let player = players.first().unwrap();
assert_eq!(player.obj.pos().y, height as f64 / 5. / 2.);
let objs = field.objs.borrow();
let player = objs.iter().find(|o| o.obj_type() == "player").unwrap();
assert_eq!(player.pos().y, height as f64 / 5. / 2.);
}
}

View File

@@ -1,21 +1,26 @@
use rstest::rstest;
use pong::game_object::components::{DefaultGeomComp, DefaultPhysicsComp};
use pong::game_object::game_object::{DefaultGameObject, GameObject};
use pong::geom::geom::{Vector};
use pong::geom::geom::Vector;
use pong::geom::shape::Shape;
use rstest::rstest;
#[rstest]
#[case(Vector::new(100., 100.), Vector::new(-1., 1.), Vector::new(99., 101.))]
pub fn should_update_pos(#[case] start_pos: Vector, #[case] vel: Vector, #[case] expected_pos: Vector) {
pub fn should_update_pos(
#[case] start_pos: Vector,
#[case] vel: Vector,
#[case] expected_pos: Vector,
) {
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
))
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();
assert_eq!(*obj.pos(), expected_pos);

View File

@@ -1,8 +1,8 @@
use rstest::rstest;
use pong::geom::geom::Vector;
use std::f64::consts::PI;
use rstest::rstest;
use std::f64::consts::FRAC_PI_2;
use std::f64::consts::FRAC_PI_4;
use std::f64::consts::PI;
#[rstest]
#[case(1., 0., 1.)]
@@ -39,7 +39,7 @@ pub fn should_normalize_correctly(
pub fn should_calculate_angle_correctly(
#[case] vector_a: Vector,
#[case] vector_b: Vector,
#[case] expected_angle: f64
#[case] expected_angle: f64,
) {
let res = vector_a.angle(&vector_b);
assert_eq!(res, expected_angle);
@@ -49,10 +49,7 @@ pub fn should_calculate_angle_correctly(
#[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);
}
@@ -63,7 +60,7 @@ pub fn should_get_orthogonal_clockwise(
#[case(Vector::new(7., 7.), Vector::new(-7., 7.))]
pub fn should_get_orthogonal_counter_clockwise(
#[case] mut vector: Vector,
#[case] expected: Vector
#[case] expected: Vector,
) {
vector.orthogonal_counter_clockwise();
assert_eq!(vector, expected);
@@ -74,7 +71,7 @@ pub fn should_get_orthogonal_counter_clockwise(
pub fn should_correctly_rotate(
#[case] mut vector: Vector,
#[case] radians: f64,
#[case] expected: Vector
#[case] expected: Vector,
) {
vector.rotate(radians);
assert_eq!(vector, expected);
@@ -87,8 +84,7 @@ pub fn should_correctly_rotate(
pub fn should_calculate_dot_product(
#[case] mut vector: Vector,
#[case] mut other: Vector,
#[case] expected: f64
#[case] expected: f64,
) {
let dot = vector.dot(&other);
assert_eq!(dot, expected);
@@ -134,4 +130,3 @@ pub fn should_reflect_vector(
vector.reflect(&onto);
assert_eq!(vector, expected);
}

View File

@@ -1,15 +1,15 @@
mod utils;
use pong::collision::collision::{Collision, CollisionDetector};
use pong::game_field::{Field, Input, InputType};
use pong::game_object::game_object::GameObject;
use pong::geom::geom::Vector;
use pong::geom::shape::ShapeType;
use pong::utils::utils::Logger;
use serde::{Deserialize, Serialize};
use serde_json::json;
use std::cmp::{max, min};
use wasm_bindgen::prelude::*;
use pong::collision::collision::{Collision, CollisionDetector};
use pong::game_field::{Field, Input, InputType};
use pong::game_object::game_object::{GameObject};
use pong::geom::geom::Vector;
use pong::geom::shape::ShapeType;
use pong::utils::utils::Logger;
extern crate serde_json;
extern crate web_sys;
@@ -48,11 +48,11 @@ impl GameObjectDTO {
y: pos.y as u16,
shape_param_1: match shape {
ShapeType::Rect(_, width, _) => *width as u16,
ShapeType::Circle(_, radius) => *radius as u16
ShapeType::Circle(_, radius) => *radius as u16,
},
shape_param_2: match shape {
ShapeType::Rect(_, _, height) => *height as u16,
ShapeType::Circle(_, _) => 0
ShapeType::Circle(_, _) => 0,
},
};
}
@@ -69,7 +69,7 @@ impl InputTypeDTO {
pub fn to_input_type(&self) -> InputType {
match self {
InputTypeDTO::UP => InputType::UP,
InputTypeDTO::DOWN => InputType::DOWN
InputTypeDTO::DOWN => InputType::DOWN,
}
}
}
@@ -86,22 +86,20 @@ impl InputDTO {
return Input {
input: self.input.to_input_type(),
obj_id: self.obj_id,
}
};
}
}
#[wasm_bindgen]
pub struct FieldWrapper {
field: Field
field: Field,
}
#[wasm_bindgen]
impl FieldWrapper {
pub fn new() -> FieldWrapper {
let field = Field::new(Box::new(WasmLogger {}));
FieldWrapper {
field
}
FieldWrapper { field }
}
pub fn width(&self) -> u16 {
@@ -114,34 +112,16 @@ impl FieldWrapper {
pub fn tick(&mut self, inputs_js: &JsValue) {
let input_dtos: Vec<InputDTO> = inputs_js.into_serde().unwrap();
let inputs = input_dtos.into_iter().map(|i| i.to_input()).collect::<Vec<Input>>();
let inputs = input_dtos
.into_iter()
.map(|i| i.to_input())
.collect::<Vec<Input>>();
self.field.tick(inputs);
log!("{:?}", self.field.collisions);
}
pub fn objects(&self) -> *const GameObjectDTO {
let mut objs = vec![];
objs.append(
&mut self.field
.balls
.iter()
.map(|ball| GameObjectDTO::from(&ball.obj))
.collect::<Vec<GameObjectDTO>>(),
);
objs.append(
&mut self.field
.players
.iter()
.map(|player| GameObjectDTO::from(&player.obj))
.collect::<Vec<GameObjectDTO>>(),
);
objs.append(
&mut self.field
.bounds.objs
.iter()
.map(|bound| GameObjectDTO::from(&bound))
.collect::<Vec<GameObjectDTO>>()
);
let mut objs = self.field.objs.borrow().iter().map(|o| GameObjectDTO::from(o)).collect::<Vec<GameObjectDTO>>();
objs.as_ptr()
}