341 lines
7.3 KiB
Rust
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()
|
|
}
|
|
}
|