use crate::prelude::*;
use bevy::reflect::Reflect;
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[derive(Clone, Reflect)]
pub struct CostField([[u8; FIELD_RESOLUTION]; FIELD_RESOLUTION]);
impl Default for CostField {
fn default() -> Self {
CostField([[1; FIELD_RESOLUTION]; FIELD_RESOLUTION])
}
}
impl Field<u8> for CostField {
fn get(&self) -> &[[u8; FIELD_RESOLUTION]; FIELD_RESOLUTION] {
&self.0
}
fn get_field_cell_value(&self, field_cell: FieldCell) -> u8 {
self.0[field_cell.get_column()][field_cell.get_row()]
}
fn set_field_cell_value(&mut self, value: u8, field_cell: FieldCell) {
self.0[field_cell.get_column()][field_cell.get_row()] = value;
}
}
impl CostField {
pub fn new_with_cost(cost: u8) -> Self {
CostField([[cost; FIELD_RESOLUTION]; FIELD_RESOLUTION])
}
pub fn is_cell_pair_reachable(&self, source: FieldCell, target: FieldCell) -> bool {
if source == target {
return true;
}
let queue = vec![source];
let mut propagation = [[false; FIELD_RESOLUTION]; FIELD_RESOLUTION];
propagation[source.get_column()][source.get_row()] = true;
process_neighbours_visibility(self, &target, queue, &mut propagation)
}
pub fn get_distance_between_cells(
&self,
source: &FieldCell,
target: &FieldCell,
) -> Option<i32> {
if source == target {
return Some(1);
}
let queue = vec![(*source, 0)];
let mut propagation = [[65535; FIELD_RESOLUTION]; FIELD_RESOLUTION];
propagation[source.get_column()][source.get_row()] = 0;
process_neighbours_distance(self, target, queue, &mut propagation)
}
#[cfg(feature = "ron")]
pub fn from_ron(path: String) -> Self {
let file = std::fs::File::open(path).expect("Failed opening CostField file");
let field: CostField = match ron::de::from_reader(file) {
Ok(field) => field,
Err(e) => panic!("Failed deserializing CostField: {}", e),
};
field
}
}
fn process_neighbours_visibility(
cost_field: &CostField,
target: &FieldCell,
queue: Vec<FieldCell>,
propagation: &mut [[bool; FIELD_RESOLUTION]; FIELD_RESOLUTION],
) -> bool {
let mut next_queue = vec![];
for cell in queue.iter() {
let neighbours = Ordinal::get_orthogonal_cell_neighbours(*cell);
for n in neighbours.iter() {
if *n == *target {
return true;
}
let cell_cost = cost_field.get_field_cell_value(*n);
if cell_cost != 255 {
let (column, row) = n.get_column_row();
let has_existing_propagation = propagation[column][row];
if !has_existing_propagation {
propagation[column][row] = true;
next_queue.push(*n);
}
}
}
}
if !next_queue.is_empty() {
process_neighbours_visibility(cost_field, target, next_queue, propagation)
} else {
false
}
}
fn process_neighbours_distance(
cost_field: &CostField,
target: &FieldCell,
queue: Vec<(FieldCell, i32)>,
propagation: &mut [[i32; FIELD_RESOLUTION]; FIELD_RESOLUTION],
) -> Option<i32> {
let mut next_queue = vec![];
for (cell, prev_cost) in queue.iter() {
let neighbours = Ordinal::get_orthogonal_cell_neighbours(*cell);
for n in neighbours {
let n_cost = cost_field.get_field_cell_value(n);
if n_cost != 255 {
let cumulative_cost = 1_i32 + prev_cost;
let (column, row) = n.get_column_row();
let existing_propagation_cost = propagation[column][row];
if cumulative_cost < existing_propagation_cost {
propagation[column][row] = cumulative_cost;
next_queue.push((n, cumulative_cost));
}
}
}
}
if !next_queue.is_empty() {
process_neighbours_distance(cost_field, target, next_queue, propagation)
} else {
let target_cumulative = propagation[target.get_column()][target.get_row()];
if target_cumulative != 65535 {
Some(target_cumulative)
} else {
None
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn get_cost_field_value() {
let mut cost_field = CostField::default();
let field_cell = FieldCell::new(9, 9);
cost_field.set_field_cell_value(255, field_cell);
let result = cost_field.get_field_cell_value(field_cell);
let actual: u8 = 255;
assert_eq!(actual, result);
}
#[test]
#[cfg(feature = "ron")]
fn cost_field_file() {
let path = env!("CARGO_MANIFEST_DIR").to_string() + "/assets/cost_field.ron";
let _cost_field = CostField::from_ron(path);
}
#[test]
fn internal_portal_visibility_true() {
let mut cost_field = CostField::default();
cost_field.set_field_cell_value(255, FieldCell::new(5, 9));
cost_field.set_field_cell_value(255, FieldCell::new(5, 8));
cost_field.set_field_cell_value(255, FieldCell::new(5, 7));
cost_field.set_field_cell_value(255, FieldCell::new(6, 7));
let source = FieldCell::new(0, 4);
let target = FieldCell::new(6, 9);
let result = cost_field.is_cell_pair_reachable(source, target);
assert!(result)
}
#[test]
fn internal_portal_visibility_false() {
let mut cost_field = CostField::default();
cost_field.set_field_cell_value(255, FieldCell::new(5, 9));
cost_field.set_field_cell_value(255, FieldCell::new(5, 8));
cost_field.set_field_cell_value(255, FieldCell::new(5, 7));
cost_field.set_field_cell_value(255, FieldCell::new(6, 7));
cost_field.set_field_cell_value(255, FieldCell::new(7, 7));
cost_field.set_field_cell_value(255, FieldCell::new(7, 8));
cost_field.set_field_cell_value(255, FieldCell::new(7, 9));
let source = FieldCell::new(0, 4);
let target = FieldCell::new(6, 9);
let result = cost_field.is_cell_pair_reachable(source, target);
let actual = false;
assert_eq!(actual, result)
}
#[test]
fn internal_cell_distance_some() {
let mut cost_field = CostField::default();
cost_field.set_field_cell_value(255, FieldCell::new(5, 9));
cost_field.set_field_cell_value(255, FieldCell::new(5, 8));
cost_field.set_field_cell_value(255, FieldCell::new(5, 7));
cost_field.set_field_cell_value(255, FieldCell::new(6, 7));
let source = FieldCell::new(0, 4);
let target = FieldCell::new(6, 9);
let result = cost_field.get_distance_between_cells(&source, &target);
assert_eq!(Some(13), result)
}
#[test]
fn internal_cell_distance_none() {
let mut cost_field = CostField::default();
cost_field.set_field_cell_value(255, FieldCell::new(5, 9));
cost_field.set_field_cell_value(255, FieldCell::new(5, 8));
cost_field.set_field_cell_value(255, FieldCell::new(5, 7));
cost_field.set_field_cell_value(255, FieldCell::new(6, 7));
cost_field.set_field_cell_value(255, FieldCell::new(7, 7));
cost_field.set_field_cell_value(255, FieldCell::new(7, 8));
cost_field.set_field_cell_value(255, FieldCell::new(7, 9));
let source = FieldCell::new(0, 4);
let target = FieldCell::new(6, 9);
let result = cost_field.get_distance_between_cells(&source, &target);
assert!(result.is_none())
}
}