use crate::{composition::grid::Grid, Rect, V2i};
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum CellState {
Cell { width: usize, height: usize },
PartOf { x: usize, y: usize },
}
#[derive(Debug, Clone, PartialEq)]
pub struct GridCombineable {
grid: Grid,
cell_states: Vec<Vec<CellState>>,
}
impl GridCombineable {
pub fn new_from_grid(grid: Grid) -> Self {
let mut cell_states = Vec::with_capacity(grid.rows);
for _ in 0..grid.rows {
let mut row = Vec::with_capacity(grid.cols);
for _ in 0..grid.cols {
row.push(CellState::Cell {
width: 1,
height: 1,
});
}
cell_states.push(row);
}
GridCombineable { grid, cell_states }
}
pub fn can_combine(&self, x: usize, y: usize, width: usize, height: usize) -> bool {
if x + width > self.grid.cols || y + height > self.grid.rows {
return false;
}
for row in y..(y + height) {
for col in x..(x + width) {
match self.cell_states[row][col] {
CellState::PartOf { .. } => return false, CellState::Cell {
width: w,
height: h,
} => {
if (w > 1 || h > 1)
&& col + w > x
&& col < x + width
&& row + h > y
&& row < y + height
{
return false;
}
}
}
}
}
true
}
pub fn set_size(
&mut self,
x: usize,
y: usize,
width: usize,
height: usize,
) -> Result<(), &'static str> {
if !self.can_combine(x, y, width, height) {
return Err("Cannot combine cells: out of bounds or cells already combined");
}
self.cell_states[y][x] = CellState::Cell { width, height };
for row in y..(y + height) {
for col in x..(x + width) {
if row != y || col != x {
self.cell_states[row][col] = CellState::PartOf { x, y };
}
}
}
Ok(())
}
pub fn get_cell(&self, x: usize, y: usize) -> Rect {
let (bl_x, bl_y, width, height) = match self.cell_states[y][x] {
CellState::Cell { width, height } => (x, y, width, height),
CellState::PartOf {
x: main_x,
y: main_y,
} => match self.cell_states[main_y][main_x] {
CellState::Cell { width, height } => (main_x, main_y, width, height),
_ => panic!("Invalid cell state: PartOf points to non-Cell"),
},
};
let bl_rect = self.grid.get_cell(bl_y, bl_x);
let tr_rect = self.grid.get_cell(bl_y + height - 1, bl_x + width - 1);
Rect::new(bl_rect.bl(), tr_rect.tr())
}
pub fn grid(&self) -> &Grid {
&self.grid
}
pub fn get_cell_state(&self, x: usize, y: usize) -> Option<&CellState> {
self.cell_states.get(y)?.get(x)
}
pub fn reset_cell(&mut self, x: usize, y: usize) -> Result<(), &'static str> {
let (main_x, main_y, width, height) = match self.cell_states[y][x] {
CellState::Cell { width, height } if width > 1 || height > 1 => (x, y, width, height),
CellState::PartOf {
x: main_x,
y: main_y,
} => match self.cell_states[main_y][main_x] {
CellState::Cell { width, height } => (main_x, main_y, width, height),
_ => return Err("Invalid cell state: PartOf points to non-Cell"),
},
_ => return Err("Cell is not part of a combined area"),
};
for row in main_y..(main_y + height) {
for col in main_x..(main_x + width) {
self.cell_states[row][col] = CellState::Cell {
width: 1,
height: 1,
};
}
}
Ok(())
}
pub fn find_main_cell(&self, x: usize, y: usize) -> (usize, usize) {
match self.cell_states[y][x] {
CellState::Cell { .. } => (x, y),
CellState::PartOf {
x: main_x,
y: main_y,
} => (main_x, main_y),
}
}
pub fn is_main_cell(&self, x: usize, y: usize) -> bool {
matches!(self.cell_states[y][x], CellState::Cell { .. })
}
pub fn iter(&self) -> GridCombineableIterator {
GridCombineableIterator {
grid_combineable: self,
current_row: 0,
current_col: 0,
visited: vec![vec![false; self.grid.cols]; self.grid.rows],
}
}
pub fn get_num_cells(&self) -> V2i {
self.grid.get_num_cells()
}
}
#[derive(Debug, Clone)]
pub struct CellInfo {
pub cell: Rect,
pub position: V2i,
pub size: V2i,
}
pub struct GridCombineableIterator<'a> {
grid_combineable: &'a GridCombineable,
current_row: usize,
current_col: usize,
visited: Vec<Vec<bool>>, }
impl Iterator for GridCombineableIterator<'_> {
type Item = CellInfo;
fn next(&mut self) -> Option<Self::Item> {
while self.current_row < self.grid_combineable.grid.rows {
if self.visited[self.current_row][self.current_col] {
self.advance_position();
continue;
}
let (main_x, main_y) = self
.grid_combineable
.find_main_cell(self.current_col, self.current_row);
if main_x != self.current_col || main_y != self.current_row {
self.visited[self.current_row][self.current_col] = true;
self.advance_position();
continue;
}
let (width, height) = match self.grid_combineable.cell_states[main_y][main_x] {
CellState::Cell { width, height } => {
for r in main_y..(main_y + height) {
for c in main_x..(main_x + width) {
self.visited[r][c] = true;
}
}
(width, height)
}
_ => {
self.visited[main_y][main_x] = true;
(1, 1)
}
};
let rect = self.grid_combineable.get_cell(main_x, main_y);
let result = CellInfo {
cell: rect,
position: V2i::new(main_x as i32, main_y as i32),
size: V2i::new(width as i32, height as i32),
};
self.advance_position();
return Some(result);
}
None
}
}
impl GridCombineableIterator<'_> {
fn advance_position(&mut self) {
self.current_col += 1;
if self.current_col >= self.grid_combineable.grid.cols {
self.current_col = 0;
self.current_row += 1;
}
}
}