mirror of
https://github.com/thilo-behnke/wasm-pong.git
synced 2026-08-23 23:26:15 +00:00
refactoring/game-obj-components
This commit is contained in:
+145
-26
@@ -1,32 +1,82 @@
|
||||
pub mod collision {
|
||||
use std::fmt::Debug;
|
||||
use crate::game_object::game_object::GameObject;
|
||||
use crate::geom::geom::Vector;
|
||||
use std::alloc::handle_alloc_error;
|
||||
use std::any::Any;
|
||||
use std::cell::{Ref, RefCell, RefMut};
|
||||
use std::collections::HashMap;
|
||||
use std::fmt::Debug;
|
||||
use std::rc::Rc;
|
||||
use crate::utils::utils::{Logger, LoggerFactory};
|
||||
|
||||
pub struct CollisionDetector {}
|
||||
pub struct CollisionDetectorConfig {
|
||||
groups: Vec<CollisionGroup>
|
||||
}
|
||||
|
||||
impl CollisionDetector {
|
||||
pub fn new() -> CollisionDetector {
|
||||
CollisionDetector {}
|
||||
impl CollisionDetectorConfig {
|
||||
pub fn new() -> CollisionDetectorConfig {
|
||||
CollisionDetectorConfig {
|
||||
groups: vec![]
|
||||
}
|
||||
}
|
||||
|
||||
pub fn detect_collisions(&self, objs: Vec<&GameObject>) -> Box<dyn CollisionRegistry> {
|
||||
pub fn matches_any_group(&self, type_a: &str, type_b: &str) -> bool {
|
||||
self.groups.iter().any(|g| g.matches(type_a, type_b))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct CollisionGroup(pub String, pub String);
|
||||
|
||||
impl CollisionGroup {
|
||||
pub fn matches(&self, type_a: &str, type_b: &str) -> bool {
|
||||
self.0 == type_a && self.1 == type_b || self.0 == type_b && self.1 == type_a
|
||||
}
|
||||
}
|
||||
|
||||
pub struct CollisionDetector {
|
||||
config: CollisionDetectorConfig,
|
||||
logger: Box<dyn Logger>
|
||||
}
|
||||
|
||||
impl CollisionDetector {
|
||||
pub fn new(logger_factory: &Box<dyn LoggerFactory>) -> CollisionDetector {
|
||||
let logger = logger_factory.get("collision_detector");
|
||||
CollisionDetector {
|
||||
config: CollisionDetectorConfig::new(),
|
||||
logger
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_groups(&mut self, groups: Vec<CollisionGroup>) {
|
||||
self.config.groups = groups;
|
||||
}
|
||||
|
||||
pub fn detect_collisions(
|
||||
&self,
|
||||
objs: Vec<Rc<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 o = &objs[i];
|
||||
let obj = RefCell::borrow(o);
|
||||
i += 1;
|
||||
|
||||
let rest = &objs[i..];
|
||||
for other in rest.iter() {
|
||||
for other in rest.iter().map(|o| o.borrow()) {
|
||||
if !self.config.matches_any_group(obj.obj_type(), other.obj_type()) {
|
||||
// self.logger.log(&*format!("objs {} and {} do not match any group: {:?}", obj.obj_type(), other.obj_type(), self.config.groups));
|
||||
continue;
|
||||
}
|
||||
let has_collision = obj.bounding_box().overlaps(&other.bounding_box());
|
||||
if !has_collision {
|
||||
continue;
|
||||
}
|
||||
collisions.push(Collision(obj.id, other.id))
|
||||
collisions.push(Collision(obj.id(), other.id()))
|
||||
}
|
||||
if i >= objs.len() {
|
||||
break;
|
||||
@@ -37,7 +87,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>;
|
||||
}
|
||||
@@ -65,28 +115,97 @@ pub mod collision {
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq)]
|
||||
pub struct Collision(pub u16, pub u16);
|
||||
|
||||
pub struct CollisionHandler {}
|
||||
pub struct CollisionHandler {
|
||||
logger: Box<dyn Logger>,
|
||||
handlers: CollisionHandlerRegistry
|
||||
}
|
||||
|
||||
impl CollisionHandler {
|
||||
pub fn new() -> CollisionHandler {
|
||||
CollisionHandler {}
|
||||
}
|
||||
pub fn handle(&self, obj_a: &mut GameObject, obj_b: &GameObject) {
|
||||
if !obj_a.is_static {
|
||||
obj_a.vel.reflect(&obj_b.orientation);
|
||||
if obj_b.vel != Vector::zero() {
|
||||
let mut adjusted = obj_b.vel.clone();
|
||||
adjusted.normalize();
|
||||
obj_a.vel.add(&adjusted);
|
||||
}
|
||||
pub fn new(logger_factory: &Box<dyn LoggerFactory>) -> CollisionHandler {
|
||||
let logger = logger_factory.get("collision_handler");
|
||||
CollisionHandler {
|
||||
logger,
|
||||
handlers: CollisionHandlerRegistry::new(),
|
||||
}
|
||||
let mut b_to_a = obj_a.pos.clone();
|
||||
b_to_a.sub(&obj_b.pos);
|
||||
b_to_a.normalize();
|
||||
obj_a.pos.add(&b_to_a);
|
||||
}
|
||||
|
||||
pub fn register(
|
||||
&mut self,
|
||||
mapping: (String, String),
|
||||
callback: fn(Rc<RefCell<Box<dyn GameObject>>>, Rc<RefCell<Box<dyn GameObject>>>),
|
||||
) {
|
||||
self.handlers.add(mapping, callback)
|
||||
}
|
||||
|
||||
pub fn handle(
|
||||
&self,
|
||||
obj_a: Rc<RefCell<Box<dyn GameObject>>>,
|
||||
obj_b: Rc<RefCell<Box<dyn GameObject>>>,
|
||||
) -> bool {
|
||||
let key = (RefCell::borrow(&obj_a).obj_type().to_string(), RefCell::borrow(&obj_b).obj_type().to_string());
|
||||
let handler_res = self.handlers.call(&key, (obj_a, obj_b));
|
||||
if !handler_res {
|
||||
self.logger.log(&*format!("Found no matching collision handler: {:?}", key));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// pub fn new() -> CollisionHandler {
|
||||
// CollisionHandler {}
|
||||
// }
|
||||
// pub fn handle(&self, obj_a: &mut Box<dyn GameObject>, obj_b: &Box<dyn GameObject>) {
|
||||
// if !obj_a.is_static() {
|
||||
// obj_a.vel_mut().reflect(&obj_b.orientation());
|
||||
// if *obj_b.vel() != Vector::zero() {
|
||||
// let mut adjusted = obj_b.vel().clone();
|
||||
// adjusted.normalize();
|
||||
// obj_a.vel_mut().add(&adjusted);
|
||||
// }
|
||||
// }
|
||||
// let mut b_to_a = obj_a.pos().clone();
|
||||
// b_to_a.sub(&obj_b.pos());
|
||||
// b_to_a.normalize();
|
||||
// obj_a.pos_mut().add(&b_to_a);
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
+220
-181
@@ -1,8 +1,15 @@
|
||||
use std::f64::consts::{FRAC_PI_2, FRAC_PI_4};
|
||||
use crate::collision::collision::{Collision, CollisionDetector, CollisionHandler, CollisionRegistry, Collisions};
|
||||
use crate::game_object::game_object::{GameObject, Shape};
|
||||
use crate::collision::collision::{Collision, CollisionDetector, CollisionGroup, 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::utils::utils::{Logger, NoopLogger};
|
||||
use crate::geom::shape::{Shape, ShapeType};
|
||||
use crate::pong::pong_collisions::{handle_ball_bounds_collision, handle_player_ball_collision, handle_player_bound_collision};
|
||||
use crate::utils::utils::{DefaultLoggerFactory, Logger, LoggerFactory, NoopLogger};
|
||||
use std::borrow::{Borrow, BorrowMut};
|
||||
use std::cell::{Cell, Ref, RefCell, RefMut};
|
||||
use std::collections::HashMap;
|
||||
use std::ops::Deref;
|
||||
use std::rc::Rc;
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum InputType {
|
||||
@@ -17,239 +24,271 @@ pub struct Input {
|
||||
}
|
||||
|
||||
pub struct Field {
|
||||
pub logger_factory: Box<dyn LoggerFactory>,
|
||||
pub logger: Box<dyn Logger>,
|
||||
pub width: u16,
|
||||
pub height: u16,
|
||||
pub players: Vec<Player>,
|
||||
pub balls: Vec<Ball>,
|
||||
pub bounds: Bounds,
|
||||
pub collisions: Box<dyn CollisionRegistry>
|
||||
pub collisions: Box<dyn CollisionRegistry>,
|
||||
objs: Vec<Rc<RefCell<Box<dyn GameObject>>>>,
|
||||
collision_detector: CollisionDetector,
|
||||
collision_handler: CollisionHandler,
|
||||
}
|
||||
|
||||
impl Field {
|
||||
pub fn new(logger: Box<dyn Logger>) -> Field {
|
||||
pub fn new(logger_factory: Box<dyn LoggerFactory>) -> Field {
|
||||
let width = 800;
|
||||
let height = 600;
|
||||
|
||||
let mut field = Field {
|
||||
logger,
|
||||
logger: logger_factory.get("game_field"),
|
||||
width,
|
||||
height,
|
||||
players: vec![],
|
||||
balls: vec![],
|
||||
bounds: Bounds::new(width, height),
|
||||
collisions: Box::new(Collisions::new(vec![]))
|
||||
objs: DefaultGameObject::bounds(width, height)
|
||||
.into_iter()
|
||||
.map(|b| Rc::new(RefCell::new(b.inner())))
|
||||
.collect(),
|
||||
collisions: Box::new(Collisions::new(vec![])),
|
||||
collision_detector: CollisionDetector::new(&logger_factory),
|
||||
collision_handler: CollisionHandler::new(&logger_factory),
|
||||
logger_factory
|
||||
};
|
||||
|
||||
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")),
|
||||
handle_player_ball_collision,
|
||||
);
|
||||
|
||||
field.collision_handler.register(
|
||||
(String::from("ball"), String::from("bound")),
|
||||
handle_ball_bounds_collision,
|
||||
);
|
||||
|
||||
field.collision_handler.register(
|
||||
(String::from("player"), String::from("bound")),
|
||||
handle_player_bound_collision,
|
||||
);
|
||||
|
||||
field.collision_detector.set_groups(
|
||||
vec![
|
||||
CollisionGroup(String::from("player"), String::from("ball")),
|
||||
CollisionGroup(String::from("player"), String::from("bound")),
|
||||
CollisionGroup(String::from("ball"), String::from("bound")),
|
||||
]
|
||||
);
|
||||
|
||||
return field;
|
||||
}
|
||||
|
||||
pub fn mock(width: u16, height: u16) -> Field {
|
||||
let logger_factory = DefaultLoggerFactory::new(Box::new(NoopLogger{}));
|
||||
Field {
|
||||
logger: Box::new(NoopLogger {}),
|
||||
logger: logger_factory.get("game_field"),
|
||||
width,
|
||||
height,
|
||||
players: vec![],
|
||||
balls: vec![],
|
||||
bounds: Bounds::new(width, height),
|
||||
collisions: Box::new(Collisions::new(vec![]))
|
||||
objs: DefaultGameObject::bounds(width, height)
|
||||
.into_iter()
|
||||
.map(|b| Rc::new(RefCell::new(b.inner())))
|
||||
.collect(),
|
||||
collisions: Box::new(Collisions::new(vec![])),
|
||||
collision_detector: CollisionDetector::new(&logger_factory),
|
||||
collision_handler: CollisionHandler::new(&logger_factory),
|
||||
logger_factory,
|
||||
}
|
||||
}
|
||||
|
||||
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.push(Rc::new(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.push(Rc::new(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.set_vel_x(-2.)
|
||||
}
|
||||
}
|
||||
|
||||
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.set_vel_y(0.);
|
||||
pub fn tick(&self, inputs: Vec<Input>) {
|
||||
for obj in self.objs.iter() {
|
||||
let mut obj_mut = RefCell::borrow_mut(obj);
|
||||
if obj_mut.obj_type() != "ball" {
|
||||
continue;
|
||||
}
|
||||
let input = input_opt.unwrap();
|
||||
match input.input {
|
||||
InputType::UP => {
|
||||
player.obj.vel.y = (player.obj.vel.y + 1.).min(5.);
|
||||
}
|
||||
InputType::DOWN => {
|
||||
player.obj.vel.y = (player.obj.vel.y - 1.).max(-5.);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
for player in self.players.iter_mut() {
|
||||
player.obj.update_pos()
|
||||
}
|
||||
for ball in self.balls.iter_mut() {
|
||||
ball.obj.update_pos()
|
||||
}
|
||||
|
||||
let mut objs: Vec<GameObject> = vec![];
|
||||
objs.extend(
|
||||
self.players
|
||||
.clone()
|
||||
.into_iter()
|
||||
.map(|p| p.obj)
|
||||
.collect::<Vec<GameObject>>(),
|
||||
);
|
||||
objs.extend(
|
||||
self.balls
|
||||
.clone()
|
||||
.into_iter()
|
||||
.map(|b| b.obj)
|
||||
.collect::<Vec<GameObject>>(),
|
||||
);
|
||||
objs.extend(
|
||||
self.bounds.objs
|
||||
.clone()
|
||||
.into_iter()
|
||||
.collect::<Vec<GameObject>>()
|
||||
);
|
||||
let collision_detector = CollisionDetector::new();
|
||||
let collision_handler = CollisionHandler::new();
|
||||
self.collisions = collision_detector.detect_collisions(objs.iter().collect());
|
||||
|
||||
for ball in self.balls.iter_mut() {
|
||||
let collisions = self.collisions.get_collisions_by_id(ball.obj.id);
|
||||
if collisions.is_empty() {
|
||||
continue;
|
||||
if *obj_mut.vel() == Vector::zero() {
|
||||
obj_mut.vel_mut().add(&Vector::new(-2.0, 0.))
|
||||
}
|
||||
let other = match collisions[0] {
|
||||
Collision(obj_a_id, obj_b_id) if *obj_a_id == ball.obj.id => {
|
||||
objs.iter().find(|o| o.id == *obj_b_id).unwrap()
|
||||
}
|
||||
|
||||
{
|
||||
for obj in self.objs.iter() {
|
||||
let mut obj_mut = RefCell::borrow_mut(obj);
|
||||
if obj_mut.obj_type() != "player" {
|
||||
continue;
|
||||
}
|
||||
collision => objs.iter().find(|o| o.id == collision.0).unwrap(),
|
||||
};
|
||||
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;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
for obj in self.objs.iter() {
|
||||
let mut obj_mut = RefCell::borrow_mut(obj);
|
||||
obj_mut.update_pos();
|
||||
}
|
||||
}
|
||||
|
||||
self.logger.log("### BEFORE COLLISION ###");
|
||||
self.logger.log(&*format!("{:?}", ball.obj));
|
||||
self.logger.log(&*format!("{:?}", other));
|
||||
collision_handler.handle(&mut ball.obj, other);
|
||||
self.logger.log("### AFTER COLLISION ###");
|
||||
self.logger.log(&*format!("{:?}", ball.obj));
|
||||
self.logger.log(&*format!("{:?}", other));
|
||||
self.logger.log("### DONE ###");
|
||||
let collisions = self.get_collisions();
|
||||
|
||||
let collision_handler = &self.collision_handler;
|
||||
let registered_collisions = collisions.get_collisions();
|
||||
self.logger.log(&*format!("Found {} collisions: {:?}", registered_collisions.len(), registered_collisions));
|
||||
for collision in registered_collisions.iter() {
|
||||
let objs = &self.objs;
|
||||
let obj_a = objs.iter().find(|o| RefCell::borrow(o).id() == collision.0).unwrap().clone();
|
||||
let obj_b = objs.iter().find(|o| RefCell::borrow(o).id() == collision.1).unwrap().clone();
|
||||
collision_handler.handle(obj_a, obj_b);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn players(&self) -> Vec<&Player> {
|
||||
self.players.iter().collect()
|
||||
fn get_collisions(&self) -> Box<dyn CollisionRegistry> {
|
||||
let objs = self.objs.iter().map(|o| o.clone()).collect();
|
||||
self.collision_detector.detect_collisions(objs)
|
||||
}
|
||||
|
||||
pub fn balls(&self) -> Vec<&Ball> {
|
||||
self.balls.iter().collect()
|
||||
pub fn objs(&self) -> Vec<&Rc<RefCell<Box<dyn GameObject>>>> {
|
||||
self.objs.iter().collect()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct Player {
|
||||
pub obj: GameObject,
|
||||
}
|
||||
|
||||
impl Player {
|
||||
pub fn new(id: u16, x: u16, y: u16, field: &Field) -> Player {
|
||||
Player {
|
||||
obj: GameObject {
|
||||
id,
|
||||
pos: Vector {x: x as f64, y: y as f64},
|
||||
orientation: Vector::new(0., 1.),
|
||||
shape: Shape::Rect,
|
||||
shape_params: vec![field.width / 25, field.height / 5],
|
||||
vel: Vector::zero(),
|
||||
is_static: true,
|
||||
},
|
||||
}
|
||||
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)),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct Ball {
|
||||
pub obj: GameObject,
|
||||
}
|
||||
|
||||
impl Ball {
|
||||
pub fn new(id: u16, x: u16, y: u16, field: &Field) -> Ball {
|
||||
Ball {
|
||||
obj: GameObject {
|
||||
id,
|
||||
pos: Vector {x: x as f64, y: y as f64},
|
||||
orientation: Vector::zero(),
|
||||
shape: Shape::Circle,
|
||||
shape_params: vec![field.width / 80],
|
||||
vel: Vector::zero(),
|
||||
is_static: false,
|
||||
},
|
||||
}
|
||||
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)),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Bounds {
|
||||
pub objs: Vec<GameObject>,
|
||||
impl DefaultGameObject {
|
||||
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(
|
||||
Vector {
|
||||
x: (width / 2) as f64,
|
||||
y: 0 as f64,
|
||||
},
|
||||
Vector::new(1., 0.),
|
||||
width as f64,
|
||||
2.,
|
||||
))),
|
||||
Box::new(DefaultPhysicsComp::new_static()),
|
||||
))),
|
||||
Bounds(Bound::TOP, 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()),
|
||||
))),
|
||||
Bounds(Bound::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()),
|
||||
))),
|
||||
Bounds(Bound::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
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum Bound {
|
||||
TOP,
|
||||
RIGHT,
|
||||
BOTTOM,
|
||||
LEFT,
|
||||
}
|
||||
|
||||
pub struct Bounds(pub Bound, pub Box<dyn GameObject>);
|
||||
|
||||
impl Bounds {
|
||||
pub fn new(width: u16, height: u16) -> Bounds {
|
||||
Bounds {
|
||||
objs: vec![
|
||||
// top
|
||||
GameObject {
|
||||
id: 90,
|
||||
pos: Vector {x: (width / 2) as f64, y: 0 as f64},
|
||||
orientation: Vector::new(1., 0.),
|
||||
shape: Shape::Rect,
|
||||
shape_params: vec![width, 2],
|
||||
is_static: true,
|
||||
vel: Vector::zero(),
|
||||
},
|
||||
// bottom
|
||||
GameObject {
|
||||
id: 91,
|
||||
pos: Vector {x: (width / 2) as f64, y: height as f64},
|
||||
orientation: Vector::new(-1., 0.),
|
||||
shape: Shape::Rect,
|
||||
shape_params: vec![width, 2],
|
||||
is_static: true,
|
||||
vel: Vector::zero(),
|
||||
},
|
||||
// left
|
||||
GameObject {
|
||||
id: 92,
|
||||
pos: Vector {x: 0 as f64, y: (height / 2) as f64},
|
||||
orientation: Vector::new(0., 1.),
|
||||
shape: Shape::Rect,
|
||||
shape_params: vec![2, height],
|
||||
is_static: true,
|
||||
vel: Vector::zero(),
|
||||
},
|
||||
// right
|
||||
GameObject {
|
||||
id: 93,
|
||||
pos: Vector {x: width as f64, y: (height / 2) as f64},
|
||||
orientation: Vector::new(0., -1.),
|
||||
shape: Shape::Rect,
|
||||
shape_params: vec![2, height],
|
||||
is_static: true,
|
||||
vel: Vector::zero(),
|
||||
},
|
||||
],
|
||||
}
|
||||
pub fn inner(self) -> Box<dyn GameObject> {
|
||||
self.1
|
||||
}
|
||||
}
|
||||
|
||||
+175
-32
@@ -1,53 +1,196 @@
|
||||
pub mod game_object {
|
||||
use crate::game_object::components::{GeomComp, PhysicsComp};
|
||||
use crate::geom::geom::{BoundingBox, Vector};
|
||||
use crate::geom::shape::{Shape, ShapeType};
|
||||
use std::fmt::Debug;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum Shape {
|
||||
Rect = 0,
|
||||
Circle = 1,
|
||||
pub trait GameObject: Debug {
|
||||
fn id(&self) -> u16;
|
||||
fn obj_type(&self) -> &str;
|
||||
fn shape(&self) -> &ShapeType;
|
||||
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;
|
||||
fn vel_mut(&mut self) -> &mut Vector;
|
||||
fn is_static(&self) -> bool;
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct GameObject {
|
||||
// #[derive(Clone, Debug, PartialEq)]
|
||||
#[derive(Debug)]
|
||||
pub struct DefaultGameObject {
|
||||
pub id: u16,
|
||||
pub pos: Vector,
|
||||
pub orientation: Vector,
|
||||
pub shape: Shape,
|
||||
pub shape_params: Vec<u16>,
|
||||
pub vel: Vector,
|
||||
pub is_static: bool,
|
||||
pub obj_type: String,
|
||||
geom: Box<dyn GeomComp>,
|
||||
physics: Box<dyn PhysicsComp>,
|
||||
}
|
||||
|
||||
impl GameObject {
|
||||
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 update_pos(&mut self) {
|
||||
self.pos.add(&self.vel);
|
||||
impl GameObject for DefaultGameObject {
|
||||
fn id(&self) -> u16 {
|
||||
self.id
|
||||
}
|
||||
|
||||
fn obj_type(&self) -> &str {
|
||||
&self.obj_type
|
||||
}
|
||||
|
||||
fn shape(&self) -> &ShapeType {
|
||||
self.geom.shape()
|
||||
}
|
||||
|
||||
fn pos(&self) -> &Vector {
|
||||
self.geom.center()
|
||||
}
|
||||
|
||||
fn pos_mut(&mut self) -> &mut Vector {
|
||||
self.geom.center_mut()
|
||||
}
|
||||
|
||||
fn orientation(&self) -> &Vector {
|
||||
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();
|
||||
center.add(&vel);
|
||||
// Keep last orientation if vel is now zero.
|
||||
if self.vel == Vector::zero() {
|
||||
if vel == Vector::zero() {
|
||||
return;
|
||||
}
|
||||
let mut orientation = self.vel.clone();
|
||||
orientation.normalize();
|
||||
self.orientation = orientation;
|
||||
let mut updated_orientation = vel.clone();
|
||||
updated_orientation.normalize();
|
||||
let orientation = self.geom.orientation_mut();
|
||||
orientation.x = updated_orientation.x;
|
||||
orientation.y = updated_orientation.y;
|
||||
}
|
||||
|
||||
pub fn set_vel_x(&mut self, x: f64) {
|
||||
self.vel.x = x
|
||||
fn bounding_box(&self) -> BoundingBox {
|
||||
self.geom.bounding_box()
|
||||
}
|
||||
|
||||
pub fn set_vel_y(&mut self, y: f64) {
|
||||
self.vel.y = y
|
||||
fn vel(&self) -> &Vector {
|
||||
&self.physics.vel()
|
||||
}
|
||||
|
||||
pub fn bounding_box(&self) -> BoundingBox {
|
||||
match self.shape {
|
||||
Shape::Rect => {
|
||||
BoundingBox::create(&self.pos, self.shape_params[0], self.shape_params[1])
|
||||
}
|
||||
Shape::Circle => {
|
||||
BoundingBox::create(&self.pos, self.shape_params[0] * 2, self.shape_params[0] * 2)
|
||||
}
|
||||
}
|
||||
fn vel_mut(&mut self) -> &mut Vector {
|
||||
self.physics.vel_mut()
|
||||
}
|
||||
|
||||
fn is_static(&self) -> bool {
|
||||
self.physics.is_static()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub mod components {
|
||||
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 std::fmt::Debug;
|
||||
|
||||
pub trait GeomComp: Debug {
|
||||
fn shape(&self) -> &ShapeType;
|
||||
fn orientation(&self) -> &Vector;
|
||||
fn orientation_mut(&mut self) -> &mut Vector;
|
||||
fn center(&self) -> &Vector;
|
||||
fn center_mut(&mut self) -> &mut Vector;
|
||||
fn bounding_box(&self) -> BoundingBox;
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct DefaultGeomComp {
|
||||
shape: ShapeType,
|
||||
}
|
||||
|
||||
impl DefaultGeomComp {
|
||||
pub fn new(shape: ShapeType) -> DefaultGeomComp {
|
||||
DefaultGeomComp { shape }
|
||||
}
|
||||
}
|
||||
|
||||
impl GeomComp for DefaultGeomComp {
|
||||
fn shape(&self) -> &ShapeType {
|
||||
&self.shape
|
||||
}
|
||||
|
||||
fn orientation(&self) -> &Vector {
|
||||
get_orientation(&self.shape)
|
||||
}
|
||||
|
||||
fn orientation_mut(&mut self) -> &mut Vector {
|
||||
get_orientation_mut(&mut self.shape)
|
||||
}
|
||||
|
||||
fn center(&self) -> &Vector {
|
||||
get_center(&self.shape)
|
||||
}
|
||||
|
||||
fn center_mut(&mut self) -> &mut Vector {
|
||||
get_center_mut(&mut self.shape)
|
||||
}
|
||||
|
||||
fn bounding_box(&self) -> BoundingBox {
|
||||
get_bounding_box(&self.shape)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait PhysicsComp: Debug {
|
||||
fn vel(&self) -> &Vector;
|
||||
fn vel_mut(&mut self) -> &mut Vector;
|
||||
fn is_static(&self) -> bool;
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct DefaultPhysicsComp {
|
||||
vel: Vector,
|
||||
is_static: bool,
|
||||
}
|
||||
impl DefaultPhysicsComp {
|
||||
pub fn new(vel: Vector, is_static: bool) -> DefaultPhysicsComp {
|
||||
DefaultPhysicsComp { vel, is_static }
|
||||
}
|
||||
|
||||
pub fn new_static() -> DefaultPhysicsComp {
|
||||
DefaultPhysicsComp::new(Vector::zero(), true)
|
||||
}
|
||||
}
|
||||
impl PhysicsComp for DefaultPhysicsComp {
|
||||
fn vel(&self) -> &Vector {
|
||||
&self.vel
|
||||
}
|
||||
|
||||
fn vel_mut(&mut self) -> &mut Vector {
|
||||
&mut self.vel
|
||||
}
|
||||
|
||||
fn is_static(&self) -> bool {
|
||||
self.is_static
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+111
-26
@@ -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 {
|
||||
@@ -110,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;
|
||||
}
|
||||
|
||||
@@ -130,11 +130,12 @@ 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()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct BoundingBox {
|
||||
top_left: Vector,
|
||||
top_right: Vector,
|
||||
@@ -143,29 +144,24 @@ pub mod geom {
|
||||
}
|
||||
|
||||
impl BoundingBox {
|
||||
pub fn create_from_coords(x: f64, y: f64, width: u16, height: u16) -> BoundingBox {
|
||||
let center = Vector::new(x, y);
|
||||
return BoundingBox::create(¢er, width, height)
|
||||
}
|
||||
|
||||
pub fn create(center: &Vector, width: u16, height: u16) -> BoundingBox {
|
||||
pub fn create(center: &Vector, width: f64, height: f64) -> BoundingBox {
|
||||
let center_x = center.x;
|
||||
let center_y = center.y;
|
||||
let top_left = Vector {
|
||||
x: center_x - (width as f64 / 2.),
|
||||
y: center_y + (height as f64 / 2.),
|
||||
x: center_x - width / 2.,
|
||||
y: center_y + height / 2.,
|
||||
};
|
||||
let top_right = Vector {
|
||||
x: center_x + (width as f64 / 2.),
|
||||
y: center_y + (height as f64 / 2.),
|
||||
x: center_x + width / 2.,
|
||||
y: center_y + height / 2.,
|
||||
};
|
||||
let bottom_left = Vector {
|
||||
x: center_x - (width as f64 / 2.),
|
||||
y: center_y - (height as f64 / 2.),
|
||||
x: center_x - width / 2.,
|
||||
y: center_y - height / 2.,
|
||||
};
|
||||
let bottom_right = Vector {
|
||||
x: center_x + (width as f64 / 2.),
|
||||
y: center_y - (height as f64 / 2.),
|
||||
x: center_x + width / 2.,
|
||||
y: center_y - height / 2.,
|
||||
};
|
||||
BoundingBox {
|
||||
top_left,
|
||||
@@ -212,13 +208,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 {
|
||||
@@ -242,3 +234,96 @@ pub mod geom {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub mod shape {
|
||||
use crate::geom::geom::{BoundingBox, Vector};
|
||||
use std::fmt::Debug;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum ShapeType {
|
||||
Rect(Shape, f64, f64),
|
||||
Circle(Shape, f64),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct Shape {
|
||||
center: Vector,
|
||||
orientation: Vector,
|
||||
}
|
||||
|
||||
impl Shape {
|
||||
pub fn rect(center: Vector, orientation: Vector, width: f64, height: f64) -> ShapeType {
|
||||
ShapeType::Rect(
|
||||
Shape {
|
||||
center,
|
||||
orientation,
|
||||
},
|
||||
width,
|
||||
height,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn circle(center: Vector, orientation: Vector, radius: f64) -> ShapeType {
|
||||
ShapeType::Circle(
|
||||
Shape {
|
||||
center,
|
||||
orientation,
|
||||
},
|
||||
radius,
|
||||
)
|
||||
}
|
||||
|
||||
fn center(&self) -> &Vector {
|
||||
&self.center
|
||||
}
|
||||
|
||||
fn center_mut(&mut self) -> &mut Vector {
|
||||
&mut self.center
|
||||
}
|
||||
|
||||
fn orientation(&self) -> &Vector {
|
||||
&self.orientation
|
||||
}
|
||||
|
||||
fn orientation_mut(&mut self) -> &mut Vector {
|
||||
&mut self.orientation
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_center(shape: &ShapeType) -> &Vector {
|
||||
match shape {
|
||||
ShapeType::Rect(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,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_orientation(shape: &ShapeType) -> &Vector {
|
||||
match shape {
|
||||
ShapeType::Rect(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,
|
||||
}
|
||||
}
|
||||
|
||||
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.)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+2
-1
@@ -1,5 +1,6 @@
|
||||
pub mod collision;
|
||||
pub mod game_field;
|
||||
pub mod game_object;
|
||||
pub mod geom;
|
||||
pub mod game_field;
|
||||
pub mod pong;
|
||||
pub mod utils;
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
pub mod pong_collisions {
|
||||
use std::cell::{RefCell, RefMut};
|
||||
use std::ops::Add;
|
||||
use std::rc::Rc;
|
||||
use crate::game_object::game_object::GameObject;
|
||||
use crate::geom::geom::Vector;
|
||||
use crate::geom::shape::ShapeType;
|
||||
|
||||
pub fn handle_player_ball_collision(
|
||||
ball: Rc<RefCell<Box<dyn GameObject>>>,
|
||||
player: Rc<RefCell<Box<dyn GameObject>>>,
|
||||
) {
|
||||
// reflect
|
||||
let mut ball = RefCell::borrow_mut(&ball);
|
||||
let player = player.borrow();
|
||||
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: Rc<RefCell<Box<dyn GameObject>>>,
|
||||
bound: Rc<RefCell<Box<dyn GameObject>>>,
|
||||
) {
|
||||
let mut ball = RefCell::borrow_mut(&ball);
|
||||
let bound = RefCell::borrow(&bound);
|
||||
ball.vel_mut().reflect(&bound.orientation());
|
||||
}
|
||||
|
||||
pub fn handle_player_bound_collision(
|
||||
player: Rc<RefCell<Box<dyn GameObject>>>,
|
||||
bound: Rc<RefCell<Box<dyn GameObject>>>,
|
||||
) {
|
||||
let mut player = RefCell::borrow_mut(&player);
|
||||
let bound = RefCell::borrow(&bound);
|
||||
let shape = player.shape().clone();
|
||||
let player_orientation = player.orientation().clone();
|
||||
let height = match shape {
|
||||
ShapeType::Rect(_, _, height) => height.clone() / 2.,
|
||||
ShapeType::Circle(_, radius) => radius,
|
||||
};
|
||||
let mut perpendicular = player_orientation.get_opposing_orthogonal(bound.orientation());
|
||||
perpendicular.y *= (height + 1.);
|
||||
let mut new_pos = bound.pos().clone();
|
||||
new_pos.add(&perpendicular);
|
||||
let player_pos = player.pos_mut();
|
||||
player_pos.y = new_pos.y;
|
||||
}
|
||||
}
|
||||
+41
-2
@@ -1,12 +1,51 @@
|
||||
|
||||
pub mod utils {
|
||||
pub trait LoggerFactory {
|
||||
fn get(&self, name: &str) -> Box<dyn Logger>;
|
||||
}
|
||||
|
||||
pub struct DefaultLoggerFactory {
|
||||
proto: Box<dyn Logger>
|
||||
}
|
||||
|
||||
impl LoggerFactory for DefaultLoggerFactory {
|
||||
fn get(&self, name: &str) -> Box<dyn Logger> {
|
||||
let mut clone = self.proto.box_clone();
|
||||
clone.set_name(name);
|
||||
clone
|
||||
}
|
||||
}
|
||||
|
||||
impl DefaultLoggerFactory {
|
||||
pub fn new(proto: Box<dyn Logger>) -> Box<dyn LoggerFactory> {
|
||||
Box::new(DefaultLoggerFactory {proto})
|
||||
}
|
||||
pub fn noop() -> Box<dyn LoggerFactory> {
|
||||
Box::new(DefaultLoggerFactory {proto: Box::new(NoopLogger {})})
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Logger {
|
||||
fn box_clone(&self) -> Box<dyn Logger>;
|
||||
fn set_name(&mut self, name: &str);
|
||||
fn log(&self, msg: &str);
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct NoopLogger {}
|
||||
|
||||
impl Logger for NoopLogger {
|
||||
fn log(&self, msg: &str) {
|
||||
fn box_clone(&self) -> Box<dyn Logger> {
|
||||
Box::new(self.clone())
|
||||
}
|
||||
|
||||
fn set_name(&mut self, name: &str) {}
|
||||
|
||||
fn log(&self, msg: &str) {}
|
||||
}
|
||||
|
||||
impl NoopLogger {
|
||||
fn new() -> Box<dyn Logger> {
|
||||
Box::new(NoopLogger {})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
use rstest::rstest;
|
||||
use pong::geom::geom::{BoundingBox, Vector};
|
||||
use rstest::rstest;
|
||||
|
||||
#[rstest]
|
||||
#[case(BoundingBox::create_from_coords(10., 10., 5, 5), Vector::new(10., 10.), true)]
|
||||
#[case(BoundingBox::create_from_coords(10., 10., 5, 5), Vector::new(8., 8.), true)]
|
||||
#[case(BoundingBox::create_from_coords(10., 10., 5, 5), Vector::new(20., 20.), false)]
|
||||
#[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,
|
||||
@@ -16,23 +16,23 @@ pub fn should_correctly_determine_if_point_is_within_box(
|
||||
|
||||
#[rstest]
|
||||
#[case(
|
||||
BoundingBox::create_from_coords(10., 10., 5, 5),
|
||||
BoundingBox::create_from_coords(10., 10., 5, 5),
|
||||
BoundingBox::create(&Vector::new(10., 10.), 5., 5.),
|
||||
BoundingBox::create(&Vector::new(10., 10.), 5., 5.),
|
||||
true
|
||||
)]
|
||||
#[case(
|
||||
BoundingBox::create_from_coords(10., 10., 5, 5),
|
||||
BoundingBox::create_from_coords(8., 8., 5, 5),
|
||||
BoundingBox::create(&Vector::new(10., 10.), 5., 5.),
|
||||
BoundingBox::create(&Vector::new(8., 8.), 5., 5.),
|
||||
true
|
||||
)]
|
||||
#[case(
|
||||
BoundingBox::create_from_coords(10., 10., 5, 5),
|
||||
BoundingBox::create_from_coords(4.9, 4.9, 5, 5),
|
||||
BoundingBox::create(&Vector::new(10., 10.), 5., 5.),
|
||||
BoundingBox::create(&Vector::new(4.9, 4.9), 5., 5.),
|
||||
false
|
||||
)]
|
||||
#[case(
|
||||
BoundingBox::create_from_coords(10., 10., 5, 5),
|
||||
BoundingBox::create_from_coords(5., 5., 5, 5),
|
||||
BoundingBox::create(&Vector::new(10., 10.), 5., 5.),
|
||||
BoundingBox::create(&Vector::new(5., 5.), 5., 5.),
|
||||
true
|
||||
)]
|
||||
pub fn should_correctly_determine_if_overlap(
|
||||
|
||||
@@ -1,73 +1,103 @@
|
||||
use rstest::rstest;
|
||||
use pong::collision::collision::CollisionHandler;
|
||||
use pong::game_object::game_object::{GameObject, Shape};
|
||||
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};
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
use pong::utils::utils::DefaultLoggerFactory;
|
||||
|
||||
#[rstest]
|
||||
#[case(
|
||||
// given
|
||||
GameObject {id: 1, pos: Vector::zero(), vel: Vector::new(1., 0.), shape: Shape::Rect, shape_params: vec![], is_static: true, orientation: Vector::new(1., 0.)},
|
||||
GameObject {id: 2, pos: Vector::zero(), vel: Vector::new(0., 0.), shape: Shape::Rect, shape_params: vec![], is_static: true, orientation: Vector::new(0., 1.)},
|
||||
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),
|
||||
// expected
|
||||
GameObject {id: 1, pos: Vector::zero(), vel: Vector::new(1., 0.), shape: Shape::Rect, shape_params: vec![], is_static: true, orientation: Vector::new(1., 0.)},
|
||||
GameObject {id: 2, pos: Vector::zero(), vel: Vector::new(0., 0.), shape: Shape::Rect, shape_params: vec![], is_static: true, orientation: Vector::new(0., 1.)},
|
||||
)]
|
||||
#[case(
|
||||
// given
|
||||
GameObject {id: 1, pos: Vector::zero(), vel: Vector::new(1., 0.), shape: Shape::Rect, shape_params: vec![], is_static: false, orientation: Vector::new(1., 0.)},
|
||||
GameObject {id: 2, pos: Vector::zero(), vel: Vector::new(0., 0.), shape: Shape::Rect, shape_params: vec![], is_static: true, orientation: Vector::new(0., 1.)},
|
||||
// expected
|
||||
GameObject {id: 1, pos: Vector::zero(), vel: Vector::new(-1., 0.), shape: Shape::Rect, shape_params: vec![], is_static: false, orientation: Vector::new(1., 0.)},
|
||||
GameObject {id: 2, pos: Vector::zero(), vel: Vector::new(0., 0.), shape: Shape::Rect, shape_params: vec![], is_static: true, orientation: Vector::new(0., 1.)},
|
||||
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(
|
||||
// given
|
||||
GameObject {id: 1, pos: Vector::zero(), vel: Vector::new(-1., 0.), shape: Shape::Rect, shape_params: vec![], is_static: false, orientation: Vector::new(-1., 0.)},
|
||||
GameObject {id: 2, pos: Vector::zero(), vel: Vector::new(0., 0.), shape: Shape::Rect, shape_params: vec![], is_static: true, orientation: Vector::new(0., 1.)},
|
||||
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),
|
||||
// expected
|
||||
GameObject {id: 1, pos: Vector::zero(), vel: Vector::new(1., 0.), shape: Shape::Rect, shape_params: vec![], is_static: false, orientation: Vector::new(-1., 0.)},
|
||||
GameObject {id: 2, pos: Vector::zero(), vel: Vector::new(0., 0.), shape: Shape::Rect, shape_params: vec![], is_static: true, orientation: Vector::new(0., 1.)},
|
||||
)]
|
||||
#[case(
|
||||
// given
|
||||
GameObject {id: 1, pos: Vector::zero(), vel: Vector::new(1., 1.), shape: Shape::Rect, shape_params: vec![], is_static: false, orientation: Vector::new(1., 1.)},
|
||||
GameObject {id: 2, pos: Vector::zero(), vel: Vector::new(0., 0.), shape: Shape::Rect, shape_params: vec![], is_static: true, orientation: Vector::new(0., 1.)},
|
||||
// expected
|
||||
GameObject {id: 1, pos: Vector::zero(), vel: Vector::new(-1., 1.), shape: Shape::Rect, shape_params: vec![], is_static: false, orientation: Vector::new(1., 1.)},
|
||||
GameObject {id: 2, pos: Vector::zero(), vel: Vector::new(0., 0.), shape: Shape::Rect, shape_params: vec![], is_static: true, orientation: Vector::new(0., 1.)},
|
||||
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(
|
||||
// given
|
||||
GameObject {id: 1, pos: Vector::zero(), vel: Vector::new(-2., 0.), shape: Shape::Rect, shape_params: vec![], is_static: false, orientation: Vector::new(-1., 0.)},
|
||||
GameObject {id: 2, pos: Vector::zero(), vel: Vector::new(0., 0.), shape: Shape::Rect, shape_params: vec![], is_static: true, orientation: Vector::new(0., 1.)},
|
||||
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),
|
||||
// expected
|
||||
GameObject {id: 1, pos: Vector::zero(), vel: Vector::new(2., 0.), shape: Shape::Rect, shape_params: vec![], is_static: false, orientation: Vector::new(-1., 0.)},
|
||||
GameObject {id: 2, pos: Vector::zero(), vel: Vector::new(0., 0.), shape: Shape::Rect, shape_params: vec![], is_static: true, orientation: Vector::new(0., 1.)},
|
||||
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(
|
||||
// given
|
||||
GameObject {id: 1, pos: Vector::zero(), vel: Vector::new(1., 0.), shape: Shape::Rect, shape_params: vec![], is_static: false, orientation: Vector::new(1., 0.)},
|
||||
GameObject {id: 2, pos: Vector::zero(), vel: Vector::new(0., 1.), shape: Shape::Rect, shape_params: vec![], is_static: true, orientation: Vector::new(0., 1.)},
|
||||
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),
|
||||
// expected
|
||||
GameObject {id: 1, pos: Vector::zero(), vel: Vector::new(-1., 1.), shape: Shape::Rect, shape_params: vec![], is_static: false, orientation: Vector::new(1., 0.)},
|
||||
GameObject {id: 2, pos: Vector::zero(), vel: Vector::new(0., 1.), shape: Shape::Rect, shape_params: vec![], is_static: true, orientation: Vector::new(0., 1.)},
|
||||
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(
|
||||
// given
|
||||
GameObject {id: 1, pos: Vector::zero(), vel: Vector::new(-2., 1.), shape: Shape::Rect, shape_params: vec![], is_static: false, orientation: Vector::new(-1., 0.)},
|
||||
GameObject {id: 2, pos: Vector::zero(), vel: Vector::new(0., 0.), shape: Shape::Rect, shape_params: vec![], is_static: true, orientation: Vector::new(0., 1.)},
|
||||
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),
|
||||
// expected
|
||||
GameObject {id: 1, pos: Vector::zero(), vel: Vector::new(2., 1.), shape: Shape::Rect, shape_params: vec![], is_static: false, orientation: Vector::new(-1., 0.)},
|
||||
GameObject {id: 2, pos: Vector::zero(), vel: Vector::new(0., 0.), shape: Shape::Rect, shape_params: vec![], is_static: true, orientation: Vector::new(0., 1.)},
|
||||
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(
|
||||
// given
|
||||
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),
|
||||
// expected
|
||||
create_game_obj(1, Vector::new(-1., 1.), Vector::new(1., 0.), false),
|
||||
create_game_obj(2, Vector::new(0., 1.), Vector::new(0., 1.), true),
|
||||
)]
|
||||
#[case(
|
||||
// given
|
||||
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),
|
||||
// expected
|
||||
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] mut obj_a: GameObject,
|
||||
#[case] obj_b: GameObject,
|
||||
#[case] expected_a: GameObject,
|
||||
#[case] expected_b: GameObject,
|
||||
#[case] mut obj_a: Rc<RefCell<Box<dyn GameObject>>>,
|
||||
#[case] mut obj_b: Rc<RefCell<Box<dyn GameObject>>>,
|
||||
#[case] expected_a: Rc<RefCell<Box<dyn GameObject>>>,
|
||||
#[case] expected_b: Rc<RefCell<Box<dyn GameObject>>>,
|
||||
) {
|
||||
let handler = CollisionHandler {};
|
||||
handler.handle(&mut obj_a, &obj_b);
|
||||
assert_eq!(obj_a, expected_a);
|
||||
assert_eq!(obj_b, expected_b);
|
||||
let logger = DefaultLoggerFactory::noop();
|
||||
let mut handler = CollisionHandler::new(&logger);
|
||||
handler.register((String::from("obj"), String::from("obj")), |a, b| {});
|
||||
let res = handler.handle(obj_a, obj_b);
|
||||
assert_eq!(true, res)
|
||||
// assert_eq!(obj_a.pos(), expected_a.pos());
|
||||
// assert_eq!(obj_a.vel(), expected_a.vel());
|
||||
// assert_eq!(obj_b.pos(), expected_b.pos());
|
||||
// assert_eq!(obj_b.vel(), expected_b.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)),
|
||||
))))
|
||||
}
|
||||
|
||||
@@ -1,46 +1,128 @@
|
||||
use pong::collision::collision::{Collision, CollisionDetector, CollisionGroup};
|
||||
use pong::game_object::game_object::GameObject;
|
||||
use pong::geom::geom::{BoundingBox, Vector};
|
||||
use pong::geom::shape::ShapeType;
|
||||
use rstest::rstest;
|
||||
use pong::collision::collision::{Collision, CollisionDetector};
|
||||
use pong::game_object::game_object::{GameObject, Shape};
|
||||
use pong::geom::geom::{Vector};
|
||||
use std::cell::{Ref, RefCell};
|
||||
use std::rc::Rc;
|
||||
use pong::utils::utils::DefaultLoggerFactory;
|
||||
|
||||
#[rstest]
|
||||
#[case(vec![], vec![])]
|
||||
#[case(
|
||||
vec![
|
||||
GameObject{id: 1, pos: Vector{x: 50., y: 50.}, shape: Shape::Rect, shape_params: vec![20, 20], vel: Vector::zero(), is_static: false, orientation: Vector::new(1., 1.)},
|
||||
GameObject{id: 2, pos: Vector{x: 50., y: 50.}, shape: Shape::Rect, shape_params: vec![20, 20], vel: Vector::zero(), is_static: false, orientation: Vector::new(1., 1.)}
|
||||
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![
|
||||
GameObject{id: 1, pos: Vector{x: 60., y: 65.}, shape: Shape::Rect, shape_params: vec![20, 20], vel: Vector::zero(), is_static: false, orientation: Vector::new(1., 1.)},
|
||||
GameObject{id: 2, pos: Vector{x: 50., y: 50.}, shape: Shape::Rect, shape_params: vec![20, 20], vel: Vector::zero(), is_static: false, orientation: Vector::new(1., 1.)}
|
||||
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![
|
||||
GameObject{id: 1, pos: Vector{x: 50., y: 50.}, shape: Shape::Rect, shape_params: vec![20, 20], vel: Vector::zero(), is_static: false, orientation: Vector::new(1., 1.)},
|
||||
GameObject{id: 2, pos: Vector{x: 80., y: 80.}, shape: Shape::Rect, shape_params: vec![20, 20], vel: Vector::zero(), is_static: false, orientation: Vector::new(1., 1.)}
|
||||
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![
|
||||
GameObject{id: 1, pos: Vector{x: 50., y: 50.}, shape: Shape::Rect, shape_params: vec![50, 50], vel: Vector::zero(), is_static: false, orientation: Vector::new(1., 1.)},
|
||||
GameObject{id: 2, pos: Vector{x: 500., y: 50.}, shape: Shape::Rect, shape_params: vec![50, 50], vel: Vector::zero(), is_static: false, orientation: Vector::new(1., 1.)}
|
||||
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<GameObject>,
|
||||
#[case] objs: Vec<Rc<RefCell<Box<dyn GameObject>>>>,
|
||||
#[case] expected_collisions: Vec<Collision>,
|
||||
) {
|
||||
let detector = CollisionDetector::new();
|
||||
let res = detector.detect_collisions(objs.iter().collect());
|
||||
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,
|
||||
zero_vec: Vector,
|
||||
}
|
||||
|
||||
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,
|
||||
zero_vec: Vector::zero(),
|
||||
})))
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
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!()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
#[cfg(test)]
|
||||
mod game_field_tests {
|
||||
use pong::game_field::{Field, Input, InputType};
|
||||
use std::borrow::Borrow;
|
||||
use std::cell::RefCell;
|
||||
|
||||
#[test]
|
||||
fn player_input_update_pos__up() {
|
||||
@@ -12,9 +14,14 @@ 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 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]
|
||||
@@ -27,38 +34,11 @@ 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.);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn player_input_update_out_of_bounds__up() {
|
||||
let height = 1000;
|
||||
let mut field = Field::mock(1000, height);
|
||||
field.add_player(1, 50, height - height / 5 / 2);
|
||||
let inputs = vec![Input {
|
||||
input: InputType::UP,
|
||||
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.);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn player_input_update_out_of_bounds__down() {
|
||||
let height = 1000;
|
||||
let mut field = Field::mock(1000, height);
|
||||
field.add_player(1, 50, height / 5 / 2);
|
||||
let inputs = vec![Input {
|
||||
input: InputType::DOWN,
|
||||
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();
|
||||
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.);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,19 +1,27 @@
|
||||
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 pong::game_object::game_object::{GameObject, Shape};
|
||||
use pong::geom::geom::{Vector};
|
||||
|
||||
#[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) {
|
||||
let mut obj = GameObject {
|
||||
id: 1,
|
||||
pos: Vector::new(start_pos.x as f64, start_pos.y as f64),
|
||||
vel,
|
||||
shape: Shape::Rect,
|
||||
shape_params: vec![],
|
||||
is_static: false,
|
||||
orientation: Vector::new(1., 0.)
|
||||
};
|
||||
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)),
|
||||
);
|
||||
obj.update_pos();
|
||||
assert_eq!(obj.pos, expected_pos);
|
||||
assert_eq!(*obj.pos(), expected_pos);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
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::{DefaultLoggerFactory, NoopLogger};
|
||||
|
||||
#[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] mut player: Rc<RefCell<Box<dyn GameObject>>>,
|
||||
#[case] mut bounds: Rc<RefCell<Box<dyn GameObject>>>,
|
||||
#[case] mut player_expected: Rc<RefCell<Box<dyn GameObject>>>,
|
||||
#[case] mut bounds_expected: Rc<RefCell<Box<dyn GameObject>>>
|
||||
) {
|
||||
handle_player_bound_collision(player.clone(), bounds.clone());
|
||||
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 field = Field::new(logger);
|
||||
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 field = Field::new(logger);
|
||||
let bounds = DefaultGameObject::bounds(field.width, field.height);
|
||||
return Rc::new(RefCell::new(bounds.into_iter().find(|b| {
|
||||
b.0 == bound
|
||||
}).unwrap().inner()));
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
+72
-64
@@ -1,14 +1,17 @@
|
||||
mod utils;
|
||||
|
||||
use std::cell::RefCell;
|
||||
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::{DefaultLoggerFactory, Logger};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::json;
|
||||
use std::cmp::{max, min};
|
||||
use std::rc::Rc;
|
||||
use wasm_bindgen::prelude::*;
|
||||
use pong::collision::collision::{Collision, CollisionDetector};
|
||||
use pong::game_field::{Field, Input, InputType};
|
||||
use pong::game_object::game_object::{GameObject, Shape};
|
||||
use pong::geom::geom::Vector;
|
||||
use pong::utils::utils::Logger;
|
||||
|
||||
extern crate serde_json;
|
||||
extern crate web_sys;
|
||||
@@ -27,30 +30,42 @@ macro_rules! log {
|
||||
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
|
||||
|
||||
#[wasm_bindgen]
|
||||
#[repr(packed)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Serialize)]
|
||||
pub struct GameObjectDTO {
|
||||
pub id: u16,
|
||||
pub x: u16,
|
||||
pub y: u16,
|
||||
pub x: f64,
|
||||
pub y: f64,
|
||||
pub orientation_x: f64,
|
||||
pub orientation_y: f64,
|
||||
pub vel_x: f64,
|
||||
pub vel_y: f64,
|
||||
pub shape_param_1: u16,
|
||||
pub shape_param_2: u16,
|
||||
}
|
||||
|
||||
impl GameObjectDTO {
|
||||
pub fn from(obj: &GameObject) -> GameObjectDTO {
|
||||
pub fn from(obj: &Rc<RefCell<Box<dyn GameObject>>>) -> GameObjectDTO {
|
||||
let obj = RefCell::borrow(obj);
|
||||
|
||||
let pos = obj.pos();
|
||||
let orientation = obj.orientation();
|
||||
let vel = obj.vel();
|
||||
let shape = obj.shape();
|
||||
return GameObjectDTO {
|
||||
id: obj.id,
|
||||
x: obj.pos.x as u16,
|
||||
y: obj.pos.y as u16,
|
||||
shape_param_1: match obj.shape_params[..] {
|
||||
[p1, _] => p1,
|
||||
[p1] => p1,
|
||||
_ => 0,
|
||||
id: obj.id(),
|
||||
x: pos.x,
|
||||
y: pos.y,
|
||||
orientation_x: orientation.x,
|
||||
orientation_y: orientation.y,
|
||||
vel_x: vel.x,
|
||||
vel_y: vel.y,
|
||||
shape_param_1: match shape {
|
||||
ShapeType::Rect(_, width, _) => *width as u16,
|
||||
ShapeType::Circle(_, radius) => *radius as u16,
|
||||
},
|
||||
shape_param_2: match obj.shape_params[..] {
|
||||
[_, p2] => p2,
|
||||
_ => 0,
|
||||
shape_param_2: match shape {
|
||||
ShapeType::Rect(_, _, height) => *height as u16,
|
||||
ShapeType::Circle(_, _) => 0,
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -67,7 +82,7 @@ impl InputTypeDTO {
|
||||
pub fn to_input_type(&self) -> InputType {
|
||||
match self {
|
||||
InputTypeDTO::UP => InputType::UP,
|
||||
InputTypeDTO::DOWN => InputType::DOWN
|
||||
InputTypeDTO::DOWN => InputType::DOWN,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -84,22 +99,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
|
||||
}
|
||||
let field = Field::new(DefaultLoggerFactory::new(Box::new(WasmLogger::root())));
|
||||
FieldWrapper { field }
|
||||
}
|
||||
|
||||
pub fn width(&self) -> u16 {
|
||||
@@ -112,52 +125,47 @@ 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>>()
|
||||
);
|
||||
objs.as_ptr()
|
||||
}
|
||||
|
||||
pub fn get_state(&self) -> String {
|
||||
let json = json!(GameObjectDTO {
|
||||
shape_param_1: 0,
|
||||
shape_param_2: 0,
|
||||
x: 10,
|
||||
y: 10,
|
||||
id: 1
|
||||
});
|
||||
pub fn objects(&self) -> String {
|
||||
let objs = self
|
||||
.field
|
||||
.objs()
|
||||
.into_iter()
|
||||
.map(|o| GameObjectDTO::from(o))
|
||||
.collect::<Vec<GameObjectDTO>>();
|
||||
let json = json!(objs);
|
||||
serde_json::to_string(&json).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct WasmLogger {}
|
||||
#[derive(Clone)]
|
||||
pub struct WasmLogger {
|
||||
name: String
|
||||
}
|
||||
|
||||
impl WasmLogger {
|
||||
pub fn root() -> WasmLogger {
|
||||
WasmLogger {name: String::from("root")}
|
||||
}
|
||||
}
|
||||
|
||||
impl Logger for WasmLogger {
|
||||
fn box_clone(&self) -> Box<dyn Logger> {
|
||||
Box::new(self.clone())
|
||||
}
|
||||
|
||||
fn set_name(&mut self, name: &str) {
|
||||
self.name = String::from(name);
|
||||
}
|
||||
|
||||
fn log(&self, msg: &str) {
|
||||
log!("{}", msg)
|
||||
log!("[{}] {}", self.name, msg)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,20 @@
|
||||
</head>
|
||||
<body>
|
||||
<noscript>This page contains webassembly and javascript content, please enable javascript in your browser.</noscript>
|
||||
<div>
|
||||
<button id="pause-btn" onclick="WASM_PONG.pauseGame()">
|
||||
Pause
|
||||
</button>
|
||||
<button id="resume-btn" onclick="WASM_PONG.resumeGame()" disabled>
|
||||
Resume
|
||||
</button>
|
||||
<button id="tick-btn" onclick="WASM_PONG.oneTick()" disabled>
|
||||
Tick
|
||||
</button>
|
||||
<button id="debug-btn" onclick="WASM_PONG.toggleDebug()">
|
||||
Debug
|
||||
</button>
|
||||
</div>
|
||||
<canvas id="wasm-app-canvas"></canvas>
|
||||
<script src="./bootstrap.js"></script>
|
||||
</body>
|
||||
|
||||
+67
-35
@@ -14,73 +14,105 @@ canvas.width = width
|
||||
|
||||
const ctx = canvas.getContext('2d');
|
||||
|
||||
let paused = false;
|
||||
let debug = false;
|
||||
let keysDown = new Set();
|
||||
|
||||
console.log(field.get_state())
|
||||
let actions = [];
|
||||
|
||||
const renderLoop = () => {
|
||||
let actions = getInputActions();
|
||||
field.tick(actions);
|
||||
|
||||
render();
|
||||
actions = getInputActions();
|
||||
if (paused) {
|
||||
requestAnimationFrame(renderLoop);
|
||||
return;
|
||||
}
|
||||
tick();
|
||||
requestAnimationFrame(renderLoop);
|
||||
}
|
||||
|
||||
const tick = () => {
|
||||
field.tick(actions);
|
||||
render();
|
||||
}
|
||||
|
||||
const render = () => {
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||
// drawField();
|
||||
drawObjects();
|
||||
}
|
||||
|
||||
const drawField = () => {
|
||||
ctx.beginPath();
|
||||
window.WASM_PONG = {}
|
||||
window.WASM_PONG.width = width
|
||||
window.WASM_PONG.height = height
|
||||
|
||||
ctx.strokeStyle = GRID_COLOR;
|
||||
ctx.rect(1, 1, field.width - 2, field.height - 2);
|
||||
window.WASM_PONG.pauseGame = () => {
|
||||
paused = true;
|
||||
document.getElementById("pause-btn").disabled = true;
|
||||
document.getElementById("resume-btn").disabled = false;
|
||||
document.getElementById("tick-btn").disabled = false;
|
||||
}
|
||||
|
||||
ctx.stroke();
|
||||
window.WASM_PONG.resumeGame = () => {
|
||||
paused = false;
|
||||
document.getElementById("pause-btn").disabled = false;
|
||||
document.getElementById("resume-btn").disabled = true;
|
||||
document.getElementById("tick-btn").disabled = true;
|
||||
}
|
||||
|
||||
|
||||
window.WASM_PONG.oneTick = () => {
|
||||
if (!paused) {
|
||||
return;
|
||||
}
|
||||
tick()
|
||||
}
|
||||
|
||||
window.WASM_PONG.toggleDebug = () => {
|
||||
debug = !debug
|
||||
}
|
||||
|
||||
const drawObjects = () => {
|
||||
const objects = getObjects();
|
||||
ctx.beginPath();
|
||||
|
||||
objects.forEach(obj => {
|
||||
ctx.beginPath();
|
||||
ctx.strokeStyle = GRID_COLOR;
|
||||
|
||||
const obj_y = height - obj.y;
|
||||
const orientation_y = obj.orientation_y * -1;
|
||||
const vel_y = obj.vel_y * -1;
|
||||
|
||||
// rect
|
||||
if (obj.shape_2) {
|
||||
ctx.moveTo(obj.x, obj.y)
|
||||
ctx.arc(obj.x, obj.y, 10, 0, 2 * Math.PI);
|
||||
ctx.rect(obj.x - obj.shape_1 / 2, obj.y - obj.shape_2 / 2, obj.shape_1, obj.shape_2);
|
||||
if (obj.shape_param_2) {
|
||||
ctx.moveTo(obj.x, obj_y)
|
||||
ctx.arc(obj.x, obj_y, 10, 0, 2 * Math.PI);
|
||||
ctx.rect(obj.x - obj.shape_param_1 / 2, obj_y - obj.shape_param_2 / 2, obj.shape_param_1, obj.shape_param_2);
|
||||
}
|
||||
// circle
|
||||
else {
|
||||
ctx.moveTo(obj.x, obj.y);
|
||||
ctx.arc(obj.x, obj.y, obj.shape_1, 0, 2 * Math.PI);
|
||||
ctx.moveTo(obj.x, obj_y);
|
||||
ctx.arc(obj.x, obj_y, obj.shape_param_1, 0, 2 * Math.PI);
|
||||
}
|
||||
ctx.stroke();
|
||||
|
||||
if (debug) {
|
||||
// velocity
|
||||
drawLine(ctx, obj.x, obj_y, obj.x + obj.vel_x * 20, obj_y + vel_y * 20, 'red')
|
||||
// orientation
|
||||
drawLine(ctx, obj.x, obj_y, obj.x + obj.orientation_x * 20, obj_y + orientation_y * 20, 'blue')
|
||||
ctx.fillText(`[x: ${obj.x}, y: ${obj_y}]`, obj.x + 10, obj_y)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const drawLine = (ctx, from_x, from_y, to_x, to_y, color) => {
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(from_x, from_y);
|
||||
ctx.strokeStyle = color;
|
||||
ctx.lineTo(to_x, to_y);
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
const getObjects = () => {
|
||||
const objectsPtr = field.objects();
|
||||
const objects = new Uint16Array(memory.buffer, objectsPtr, 3 * 5 + 4 * 5) // player1, player2, ball + 4x bounds
|
||||
.reduce((acc, val) => {
|
||||
if (!acc.length) {
|
||||
return [[val]]
|
||||
}
|
||||
const last = acc[acc.length - 1]
|
||||
if (last.length === 5) {
|
||||
return [...acc, [val]]
|
||||
}
|
||||
return [...acc.slice(0, -1), [...last, val]]
|
||||
}, [])
|
||||
.map(([id, x, y, shape_1, shape_2]) => {
|
||||
return {id, x, y: height - y, shape_1, shape_2};
|
||||
});
|
||||
return objects;
|
||||
return JSON.parse(field.objects());
|
||||
}
|
||||
|
||||
const listenToKeys = () => {
|
||||
|
||||
Reference in New Issue
Block a user