use macroquad::prelude::*; use serde::{Deserialize, Serialize}; use super::{tile::*, Pos, PosInt}; use crate::{ theme::TILE_TEXTURE_SIZE, util::{draw_scaled_texture, Textures}, }; #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(into = "String", from = "String")] pub struct Grid { tiles: Vec, width: usize, height: usize, } #[derive(Debug, Clone)] pub struct ResizeDeltas { pub x_pos: usize, pub x_neg: usize, pub y_pos: usize, pub y_neg: usize, } impl ResizeDeltas { pub fn new( margin: usize, (width, height): (usize, usize), pos: Pos, (new_width, new_height): (usize, usize), ) -> Self { let margin = margin as PosInt; let width = width as PosInt; let height = height as PosInt; let new_width = new_width as PosInt; let new_height = new_height as PosInt; Self { x_pos: if (pos.x + margin + new_width) > width { pos.x + margin + new_width - width } else { 0 } as usize, x_neg: if pos.x < margin { margin - pos.x } else { 0 } as usize, y_pos: if (pos.y + margin + new_height) > height { pos.y + margin + new_height - height } else { 0 } as usize, y_neg: if pos.y < margin { margin - pos.y } else { 0 } as usize, } } } impl Grid { pub fn from_ascii(source: &str) -> Self { let mut rows = Vec::new(); let mut width = 0; let mut height = 0; for line in source.lines() { height += 1; width = width.max(line.len()); let mut tiles = Vec::new(); for char in line.chars() { tiles.push(Tile::from_char(char)); } rows.push(tiles); } for line in &mut rows { line.resize(width, Tile::BLANK); } let tiles = rows.into_iter().flatten().collect(); Self { tiles, width, height, } } pub fn to_ascii(&self) -> String { let mut out = String::new(); for y in 0..self.height { for x in 0..self.width { let tile = self.get((x, y).into()).unwrap(); out.push(tile.to_char()); } if y > 0 { while out.as_bytes().last() == Some(&b' ') { out.pop(); } } out.push('\n'); } out } pub fn new_empty(width: usize, height: usize) -> Self { let tiles = vec![Tile::BLANK; width * height]; Self { tiles, width, height, } } pub fn new_single(tile: Tile) -> Self { Self { tiles: vec![tile], width: 1, height: 1, } } pub fn count_tiles(&self) -> usize { let mut sum = 0; for tile in &self.tiles { if !matches!(tile, Tile::Open(OpenTile::Blank, _) | Tile::Block) { sum += 1 } } sum } pub fn used_bounds_area(&self) -> usize { let row_clear = |y| { for x in 0..self.width { if !self.get_unchecked((x, y).into()).is_blank() { return false; } } true }; let mut height = self.height; for y in 0..self.height { if row_clear(y) { height -= 1; } else { break; } } for y in (0..self.height).rev() { if row_clear(y) { height -= 1; } else { break; } } let col_clear = |x| { for y in 0..self.height { if !self.get_unchecked((x, y).into()).is_blank() { return false; } } true }; let mut width = self.width; for x in 0..self.width { if col_clear(x) { width -= 1; } else { break; } } for x in (0..self.width).rev() { if col_clear(x) { width -= 1; } else { break; } } width * height } fn in_bounds(&self, p: Pos) -> bool { p.x >= 0 && p.y >= 0 && p.x < self.width as PosInt && p.y < self.height as PosInt } fn get_unchecked(&self, p: Pos) -> Tile { self.tiles[p.y as usize * self.width + p.x as usize] } pub fn get(&self, p: Pos) -> Option { if self.in_bounds(p) { Some(self.get_unchecked(p)) } else { None } } pub fn get_or_blank(&self, p: Pos) -> Tile { if self.in_bounds(p) { self.get_unchecked(p) } else { Tile::BLANK } } pub fn get_mut(&mut self, p: Pos) -> Option<&mut Tile> { if self.in_bounds(p) { Some(&mut self.tiles[p.y as usize * self.width + p.x as usize]) } else { None } } pub fn set(&mut self, p: Pos, tile: Tile) { if self.in_bounds(p) { self.tiles[p.y as usize * self.width + p.x as usize] = tile; } } pub fn paste_grid(&mut self, pos: Pos, source: &Grid) { for x in 0..source.width() { for y in 0..source.height() { let offset = (x, y).into(); if let Some(tile) = source.get(offset) { self.set(offset + pos, tile); } } } } pub fn get_rect(&self, pos: Pos, width: usize, height: usize) -> Grid { let mut out = Grid::new_empty(width, height); for x in 0..width { for y in 0..height { let offset = (x, y).into(); if let Some(tile) = self.get(offset + pos) { out.set(offset, tile); } } } out } pub fn grow(&mut self, deltas: &ResizeDeltas) { let new_width = self.width + deltas.x_neg + deltas.x_pos; let new_height = self.height + deltas.y_neg + deltas.y_pos; let mut new_grid = Grid::new_empty(new_width, new_height); for x in 0..self.width { for y in 0..self.height { let tile = self.get_unchecked((x, y).into()); new_grid.set((x + deltas.x_neg, y + deltas.y_neg).into(), tile); } } *self = new_grid; } pub fn shrink(&mut self, deltas: &ResizeDeltas) { let new_width = self.width - deltas.x_neg - deltas.x_pos; let new_height = self.height - deltas.y_neg - deltas.y_pos; let mut new_grid = Grid::new_empty(new_width, new_height); for x in 0..new_width { for y in 0..new_height { let tile = self.get_unchecked((x + deltas.x_neg, y + deltas.y_neg).into()); new_grid.set((x, y).into(), tile); } } *self = new_grid; } pub fn width(&self) -> usize { self.width } pub fn height(&self) -> usize { self.height } pub fn size(&self) -> (usize, usize) { (self.width, self.height) } pub fn get_marbles(&self) -> Vec { let mut out = Vec::new(); for y in 0..self.height { for x in 0..self.width { if let Tile::Marble { value: _, dir: _ } = self.get_unchecked((x, y).into()) { out.push((x, y).into()); } } } out } pub fn draw( &self, textures: &Textures, offset: Vec2, scale: f32, power_directions: bool, ) { let tile_size = (TILE_TEXTURE_SIZE * scale) as i32; let start_x = (-offset.x as i32) / tile_size - 1; let tiles_width = screen_width() as i32/ tile_size + 3; let start_y = (-offset.y as i32) / tile_size - 1; let tiles_height = screen_height() as i32 / tile_size + 3; for x in start_x..(start_x + tiles_width) { for y in start_y..(start_y + tiles_height) { let px = (x * tile_size) as f32 + offset.x; let py = (y * tile_size) as f32 + offset.y; if let Some(tile) = self.get((x, y).into()) { let texname = tile.texture(); if texname.is_empty() { continue; } let texture = textures.get(texname); draw_scaled_texture( texture, px, py, scale); if power_directions { if let Tile::Powerable(_, state) = &tile { for dir in Direction::ALL { if state.get_dir(dir) { let texture = textures.get(dir.debug_arrow_texture_name()); draw_scaled_texture( texture, px, py, scale); } } } } } else { draw_rectangle(px, py, tile_size as _, tile_size as _, Color::from_rgba(0, 0, 0, 80)); } } } } } impl From for Grid { fn from(value: String) -> Self { Self::from_ascii(&value) } } impl From for String { fn from(val: Grid) -> String { val.to_ascii() } }