Skip to content

Commit

Permalink
refactor(backend): refactor code to use the 'Coordinate' struct inste…
Browse files Browse the repository at this point in the history
…ad disperse variables
  • Loading branch information
iagorrr committed Dec 2, 2024
1 parent dff7f38 commit 3170223
Show file tree
Hide file tree
Showing 3 changed files with 65 additions and 104 deletions.
18 changes: 7 additions & 11 deletions backend/api/src/game.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use crate::solver::Coordinate;
use serde::{Deserialize, Serialize};

#[derive(Serialize, Deserialize, Debug, Eq, Copy, Clone, PartialEq, PartialOrd, Ord, Hash)]
Expand Down Expand Up @@ -26,13 +27,13 @@ impl CellPiece {
}
}

pub fn can_connect(u_x: i32, u_y: i32, u: &CellPiece, v_x: i32, v_y: i32, v: &CellPiece) -> bool {
if u_x == v_x && u_y == v_y {
pub fn can_connect(u_pos: &Coordinate, u: &CellPiece, v_pos: &Coordinate, v: &CellPiece) -> bool {
if u_pos == v_pos {
return true;
}

let d_x: i32 = u_x - v_x;
let d_y: i32 = u_y - v_y;
let d_x: i32 = u_pos.x - v_pos.x;
let d_y: i32 = u_pos.y - v_pos.y;

if d_x != 0 && d_y != 0 {
// diagonal
Expand Down Expand Up @@ -61,14 +62,9 @@ pub type CellGrid = Vec<Vec<CellPiece>>;

#[derive(Serialize, Deserialize, Debug)]
pub struct GameSchema {
pub number_of_rows: i32,
pub number_of_columns: i32,
pub source: Coordinate,

pub initial_x: i32,
pub initial_y: i32,

pub target_x: i32,
pub target_y: i32,
pub goal: Coordinate,

pub grid: CellGrid,
}
31 changes: 13 additions & 18 deletions backend/api/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
mod game;
use game::GameSchema;
use solver::Coordinate;

mod solver;
use solver::is_solved;
use solver::{is_solved, solve};

use actix_web::{
get,
Expand All @@ -12,13 +11,18 @@ use actix_web::{
};

use std::env;

use std::time::Duration;

use tokio::time::timeout;

#[get("/solver")]
async fn solver_endpoint(data: web::Json<GameSchema>) -> impl Responder {
let result = timeout(Duration::from_secs(13), solver::solver(&data.0)).await;
// MAKE SURE IT'S A RECTANGLE MAP
let result = timeout(
Duration::from_secs(13),
solve(&data.0.source, &data.0.goal, &data.0.grid),
)
.await;
return match result {
Ok(ret) => match ret {
Some(ret) => HttpResponse::Ok().json(ret),
Expand All @@ -29,23 +33,14 @@ async fn solver_endpoint(data: web::Json<GameSchema>) -> impl Responder {
}

#[get("/is_solved")]
async fn is_solvable_endpoint(game: web::Json<GameSchema>) -> impl Responder {
solver::print_grid(&game.0.grid);
return HttpResponse::Ok().json(is_solved(
&game.0.grid,
Coordinate {
x: game.initial_x,
y: game.initial_y,
},
Coordinate {
x: game.0.target_x,
y: game.0.target_y,
},
));
async fn is_solved_endpoint(game: web::Json<GameSchema>) -> impl Responder {
return HttpResponse::Ok().json(is_solved(&game.0.source, &game.0.goal, &game.0.grid));
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
// Defines which port to run the API based in ENVIRONMENT variable
// BACKEND_API_PORT, if no such variable is fond 8086 is used
let backend_api_port: u16 = match env::var("BACKEND_API_PORT") {
Ok(val) => val.parse::<u16>().unwrap(),
Err(_e) => 8086,
Expand All @@ -54,7 +49,7 @@ async fn main() -> std::io::Result<()> {
HttpServer::new(|| {
App::new()
.service(solver_endpoint)
.service(is_solvable_endpoint)
.service(is_solved_endpoint)
})
.bind(("0.0.0.0", backend_api_port))?
.run()
Expand Down
120 changes: 45 additions & 75 deletions backend/api/src/solver.rs
Original file line number Diff line number Diff line change
@@ -1,40 +1,20 @@
use serde::{Deserialize, Serialize};

use crate::game::can_connect;
use crate::game::CellGrid;
use crate::game::GameSchema;
use std::collections::BinaryHeap;
use std::collections::HashMap;
use std::collections::HashSet;
use std::collections::VecDeque;
use crate::game::{can_connect, CellGrid, GameSchema};

use std::collections::{BinaryHeap, HashMap, HashSet, VecDeque};

// State to be added at the A* min_heap
// TODO: Don't use the the negative of distance and actually implement a Ord properly
#[derive(Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Hash)]
struct State {
manhattan_negative_distance: i32,
current_x: i32,
current_y: i32,
pos: Coordinate,
grid: CellGrid,
}

fn calc_manhattan_negative_distance(u_x: i32, u_y: i32, v_x: i32, v_y: i32) -> i32 {
-((u_x - v_x).abs() + (u_y - v_y).abs())
}

// TODO: this can be done with traits right ?
fn get_state(game: &GameSchema) -> State {
State {
manhattan_negative_distance: calc_manhattan_negative_distance(
game.initial_x,
game.initial_y,
game.target_x,
game.target_y,
),
current_x: game.initial_x,
current_y: game.initial_y,
grid: game.grid.clone(),
}
fn calc_manhattan_negative_distance(u: &Coordinate, v: &Coordinate) -> i32 {
-((u.x - v.x).abs() + (u.y - v.y).abs())
}

// 5 directions, center, up, right, down, left (clockwise)
Expand Down Expand Up @@ -64,78 +44,74 @@ pub fn print_grid(grid: &CellGrid) {
}
}

pub async fn solver(game: &GameSchema) -> Option<GameSchema> {
pub async fn solve(
source: &Coordinate,
goal: &Coordinate,
initial_grid: &CellGrid,
) -> Option<GameSchema> {
let mut heap: BinaryHeap<State> = BinaryHeap::new();
let mut vis: HashSet<State> = HashSet::new();

let initial_state = get_state(&game);
let initial_state = State {
manhattan_negative_distance: calc_manhattan_negative_distance(&source, &goal),
pos: source.clone(),
grid: initial_grid.clone(),
};

heap.push(initial_state.clone());
vis.insert(initial_state.clone());

let target_x = game.target_x;
let target_y = game.target_y;
while let Some(State {
manhattan_negative_distance: u_dist,
current_x: u_x,
current_y: u_y,
pos: u_pos,
grid: mut u_grid,
}) = heap.pop()
{
// Found solution.
if u_dist == 0 {
print_grid(&u_grid);
return Some(GameSchema {
initial_x: game.initial_x,
initial_y: game.initial_y,
target_x: u_x,
target_y: u_y,
number_of_rows: game.number_of_rows,
number_of_columns: game.number_of_columns,
// TODO: shorthand?
source: source.clone(),
goal: goal.clone(),
grid: u_grid,
});
}

for d in 0..5 {
let v_x = u_x + DELTA_X[d];
let v_y = u_y + DELTA_Y[d];
if v_x >= 0 && v_x < game.number_of_rows && v_y >= 0 && v_y < game.number_of_columns {
let mut v_pos = u_pos.clone();
v_pos.x += DELTA_X[d];
v_pos.y += DELTA_Y[d];
if v_pos.x >= 0
&& v_pos.x < initial_grid.len() as i32
&& v_pos.y >= 0
&& v_pos.y < initial_grid[v_pos.x as usize].len() as i32
{
for _r in 0..4 {
let connectable = can_connect(
u_x,
u_y,
&u_grid[u_x as usize][u_y as usize],
v_x,
v_y,
&u_grid[v_x as usize][v_y as usize],
&u_pos,
&u_grid[u_pos.x as usize][u_pos.y as usize],
&v_pos,
&u_grid[v_pos.x as usize][v_pos.y as usize],
);
if connectable {
let v = State {
manhattan_negative_distance: calc_manhattan_negative_distance(
v_x, v_y, target_x, target_y,
&v_pos, &goal,
),
current_x: v_x,
current_y: v_y,
pos: v_pos,
grid: u_grid.clone(),
};

if is_solved(
&v.grid,
Coordinate {
x: game.initial_x,
y: game.initial_y,
},
Coordinate { x: v_x, y: v_y },
)
.solvable
&& !vis.contains(&v)
{
// TODO: don't need to check the whole map, just if
// is still connect to it's parent...
if is_solved(&source, &v_pos, &v.grid).solvable && !vis.contains(&v) {
vis.insert(v.clone());
heap.push(v.clone());
}
}

u_grid[v_x as usize][v_y as usize].rotate();
u_grid[v_pos.x as usize][v_pos.y as usize].rotate();
}
}
}
Expand All @@ -144,7 +120,7 @@ pub async fn solver(game: &GameSchema) -> Option<GameSchema> {
None
}

#[derive(Serialize, Deserialize, Debug, Clone, Hash, Eq, PartialEq, Copy)]
#[derive(Serialize, Deserialize, Debug, Clone, Hash, Eq, PartialEq, Copy, Ord, PartialOrd)]
pub struct Coordinate {
pub x: i32,
pub y: i32,
Expand All @@ -156,22 +132,18 @@ pub struct Solution {
pub coordinates: Vec<Coordinate>, // TODO: probably better as a result
}

// EEEEEEEEEEEEEEEEEEE trem feio da porra KKKKKKKKKKKKKKKKKKKKKKKK
pub fn is_solved(grid: &CellGrid, source: Coordinate, target: Coordinate) -> Solution {
pub fn is_solved(source: &Coordinate, goal: &Coordinate, grid: &CellGrid) -> Solution {
let n = grid.len();
let m = grid[0].len();
let mut vis = vec![vec![false; m]; n];
let mut parent_coordinate: HashMap<Coordinate, Coordinate> = HashMap::new();

let mut queue = VecDeque::from([Coordinate {
x: source.x,
y: source.y,
}]);
let mut queue = VecDeque::from([source.clone()]);

vis[source.x as usize][source.y as usize] = true;

while let Some(u) = queue.pop_front() {
if u.x == target.x && u.y == target.y {
if u == *goal {
let mut coordinates: Vec<Coordinate> = vec![];
let mut current = u;
loop {
Expand All @@ -183,7 +155,7 @@ pub fn is_solved(grid: &CellGrid, source: Coordinate, target: Coordinate) -> Sol
}
return Solution {
solvable: true,
coordinates: coordinates,
coordinates: coordinates, // TODO: shorthand ?
};
}

Expand All @@ -199,11 +171,9 @@ pub fn is_solved(grid: &CellGrid, source: Coordinate, target: Coordinate) -> Sol
}

if !can_connect(
u.x,
u.y,
&u,
&grid[u.x as usize][u.y as usize],
v.x,
v.y,
&v,
&grid[v.x as usize][v.y as usize],
) {
continue;
Expand Down

0 comments on commit 3170223

Please sign in to comment.