marble-machinations/src/marble_engine/grid.rs

341 lines
7.3 KiB
Rust

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<Tile>,
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<Tile> {
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<Pos> {
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<String> for Grid {
fn from(value: String) -> Self {
Self::from_ascii(&value)
}
}
impl From<Grid> for String {
fn from(val: Grid) -> String {
val.to_ascii()
}
}