Skip to content

Commit

Permalink
refactor(global): abstracted collision mechanism and all the parts wi…
Browse files Browse the repository at this point in the history
…th their own structs
  • Loading branch information
kriskw1999 committed Jun 16, 2024
1 parent dd946af commit 57259db
Show file tree
Hide file tree
Showing 6 changed files with 481 additions and 156 deletions.
201 changes: 201 additions & 0 deletions src/collision.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
use crate::coord::Coord;

fn is_point_on_line(point_coord: &Coord, (start_coord, end_coord): (&Coord, &Coord)) -> bool {
let Coord { x: px, y: py } = point_coord;
let Coord { x: x1, y: y1 } = start_coord;
let Coord { x: x2, y: y2 } = end_coord;

let cross_product = (py - y1) * (x2 - x1) - (px - x1) * (y2 - y1);
if cross_product.abs() > std::f64::EPSILON {
return false;
}

let dot_product = (px - x1) * (x2 - x1) + (py - y1) * (y2 - y1);
if dot_product < 0.0 {
return false;
}

let squared_length = (x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1);
if dot_product > squared_length {
return false;
}

true
}

pub fn check_collisions(collidable1: &dyn Collidable, collidable2: &dyn Collidable) -> bool {
let border1 = collidable1.get_border();
let border2 = collidable2.get_border();

// collision between points
if border1.len() == 1 && border2.len() == 1 {
if border1[0].x == border2[0].x && border1[0].y == border2[0].y {
return true;
}
}
// collision between point and line
else if border1.len() == 1 || border2.len() == 1 {
let (point, borders) = if border1.len() == 1 {
(&border1[0], &border2)
} else {
(&border2[0], &border1)
};

for line in borders.windows(2) {
if is_point_on_line(&point, (&line[0], &line[1])) {
return true;
}
}
}
// collision between two lines or shapes
else {
for line1 in border1.windows(2) {
for line2 in border2.windows(2) {
let x1 = line1[0].x;
let y1 = line1[0].y;
let x2 = line1[1].x;
let y2 = line1[1].y;

let x3 = line2[0].x;
let y3 = line2[0].y;
let x4 = line2[1].x;
let y4 = line2[1].y;

let den = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4);

if den == 0.0 {
continue;
}

let t = ((x1 - x3) * (y3 - y4) - (y1 - y3) * (x3 - x4)) / den;
let u = -((x1 - x2) * (y1 - y3) - (y1 - y2) * (x1 - x3)) / den;

if t >= 0.0 && t <= 1.0 && u >= 0.0 && u <= 1.0 {
return true;
}
}
}
}

false
}

pub trait Collidable {
fn get_border(&self) -> Vec<Coord>;
}

#[cfg(test)]
mod tests {
use super::*;

struct TestCollidable {
border: Vec<Coord>,
}

impl Collidable for TestCollidable {
fn get_border(&self) -> Vec<Coord> {
self.border.clone()
}
}

#[test]
fn test_collision_manager() {
let collidable1 = TestCollidable {
border: vec![
Coord::new(0.0, 0.0),
Coord::new(0.0, 5.0),
Coord::new(5.0, 5.0),
Coord::new(5.0, 0.0),
],
};

let collidable2 = TestCollidable {
border: vec![
Coord::new(0.0, 5.0),
Coord::new(1.5, 0.0),
Coord::new(2.5, 2.5),
Coord::new(3.5, 0.0),
Coord::new(5.0, 5.0),
],
};

let collided = check_collisions(&collidable1, &collidable2);

assert_eq!(collided, true);
}

#[test]
fn test_no_collision() {
let collidable1 = TestCollidable {
border: vec![
Coord::new(0.0, 0.0),
Coord::new(0.0, 1.0),
Coord::new(1.0, 1.0),
Coord::new(1.0, 0.0),
],
};

let collidable2 = TestCollidable {
border: vec![
Coord::new(2.0, 0.0),
Coord::new(3.0, 0.0),
Coord::new(2.0, 1.0),
Coord::new(3.0, 1.0),
],
};

let collided = check_collisions(&collidable1, &collidable2);

assert_eq!(collided, false);
}

#[test]
fn test_no_collision_with_same_border() {
let collidable1 = TestCollidable {
border: vec![
Coord::new(0.0, 0.0),
Coord::new(0.0, 1.0),
Coord::new(1.0, 1.0),
Coord::new(1.0, 0.0),
],
};

let collidable2 = TestCollidable {
border: vec![Coord::new(0.0, 0.0)],
};

let collided = check_collisions(&collidable1, &collidable2);

assert_eq!(collided, true);
}

#[test]
fn test_collision_of_two_points() {
let collidable1 = TestCollidable {
border: vec![Coord::new(0.0, 0.0)],
};

let collidable2 = TestCollidable {
border: vec![Coord::new(0.0, 0.0)],
};

let collided = check_collisions(&collidable1, &collidable2);

assert_eq!(collided, true);
}

#[test]
fn test_collision_of_points_over_a_line() {
let collidable1 = TestCollidable {
border: vec![Coord::new(0.0, 0.0), Coord::new(9.0, 0.0)],
};

let collidable2 = TestCollidable {
border: vec![Coord::new(5.0, 0.0)],
};

let collided = check_collisions(&collidable1, &collidable2);

assert_eq!(collided, true);
}
}
71 changes: 0 additions & 71 deletions src/game.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
use crate::coord::Coord;

const SPEED: u8 = 8;

#[derive(PartialEq)]
Expand All @@ -20,12 +18,7 @@ pub enum GameState {

pub struct Game {
pub score: i32,
pub total_length: i32,
pub state: GameState,
pub corners: Vec<Coord>,
pub head_coord: Coord,
pub direction: Direction,
pub point_coord: Option<Coord>,
pub frame_num: i32,
speed: u8,
}
Expand All @@ -35,19 +28,13 @@ impl Game {
Game {
score: 0,
state: GameState::Startup,
corners: Vec::from([Coord::new(-12.0, 10.0)]),
total_length: 12,
head_coord: Coord::new(0.0, 10.0),
direction: Direction::Right,
point_coord: None,
frame_num: 0,
speed: SPEED,
}
}

pub fn increase_score(&mut self) {
self.score += 1;
self.total_length += 2;
}

pub fn increase_frame_num(&mut self) {
Expand All @@ -60,71 +47,13 @@ impl Game {
};
}

pub fn move_head(&mut self) {
match self.direction {
Direction::Up => self.head_coord.move_up(1.0),
Direction::Down => self.head_coord.move_down(1.0),
Direction::Left => self.head_coord.move_left(1.0),
Direction::Right => self.head_coord.move_right(1.0),
}
}

pub fn game_over(&mut self) {
self.state = GameState::GameOver;
}

pub fn move_tail(&mut self) {
let tail_coord = &self.corners[0];

if self.corners.len() == 1 {
let tail_distance_x = Coord::get_x_distance(tail_coord, &self.head_coord);
let tail_distance_y = Coord::get_y_distance(tail_coord, &self.head_coord);

if tail_distance_x + tail_distance_y > (self.total_length as f64) {
self.corners[0].move_toward(&self.head_coord);
}
} else {
let next_corner = self.corners[1].clone();
self.corners[0].move_toward(&next_corner);

if self.corners[0].compare(&next_corner) {
self.corners.remove(0);
}
}
}

pub fn change_direction(&mut self, direction: Direction) {
self.direction = direction;
self.corners
.push(Coord::new(self.head_coord.x, self.head_coord.y))
}

pub fn restart(&mut self) {
self.score = 0;
self.total_length = 12;
self.state = GameState::Running;
self.corners = Vec::from([Coord::new(-12.0, 10.0)]);
self.head_coord = Coord::new(0.0, 10.0);
self.direction = Direction::Right;
self.point_coord = None;
self.frame_num = 0;
}

pub fn generate_new_point(&mut self, width: f64, height: f64) {
self.point_coord = Some(Coord::randomize(width - 1.0, height - 1.0));
}

pub fn check_beat(&mut self) {
self.corners.windows(2).for_each(|pair| {
let start = &pair[0];
let end = &pair[1];

// check if the head coord is between start and end
if (self.head_coord.x - start.x) * (self.head_coord.x - end.x) <= 0.0
&& (self.head_coord.y - start.y) * (self.head_coord.y - end.y) <= 0.0
{
self.state = GameState::GameOver;
}
});
}
}
Loading

0 comments on commit 57259db

Please sign in to comment.