Skip to content

Commit

Permalink
Chapter 03. Tile map generation
Browse files Browse the repository at this point in the history
  • Loading branch information
leonidv committed Nov 26, 2023
1 parent f95f83b commit 45e742b
Show file tree
Hide file tree
Showing 5 changed files with 239 additions and 0 deletions.
52 changes: 52 additions & 0 deletions board_plugin/src/components/coordinates.rs
@@ -0,0 +1,52 @@
use std::fmt::{self, Display, Formatter};
use std::ops::{Add, Sub};
use bevy::prelude::Component;

use bevy_inspector_egui::{prelude::*, reflect_inspector};

// adopted https://github.com/jakobhellermann/bevy-inspector-egui/blob/main/docs/MIGRATION_GUIDE_0.15_0.16.md
#[cfg_attr(feature = "debug", derive(InspectorOptions))]
#[derive(Clone, Copy)]
// todo
pub struct Coordinates {
pub x: u16,
pub y: u16
}

impl Add for Coordinates {
type Output = Self;

fn add(self, rhs: Self) -> Self::Output {
Self {
x: self.x + rhs.x,
y: self.y + rhs.y
}
}
}

impl Add<(i8,i8)> for Coordinates {
type Output = Self;

fn add(self, (rhs_x, rhs_y): (i8,i8)) -> Self::Output {
let x = ((self.x as i16) + rhs_x as i16) as u16;
let y = ((self.y as i16) + rhs_y as i16) as u16;
Self {x, y}
}
}

impl Sub for Coordinates {
type Output = Self;

fn sub(self, rhs: Self) -> Self::Output {
Self{
x: self.x.saturating_sub(rhs.x),
y: self.y.saturating_sub(rhs.y)
}
}
}

impl Display for Coordinates {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "({},{})", self.x, self.y)
}
}
2 changes: 2 additions & 0 deletions board_plugin/src/components/mod.rs
@@ -0,0 +1,2 @@
pub use coordinates::Coordinates;
mod coordinates;
2 changes: 2 additions & 0 deletions board_plugin/src/resources/mod.rs
@@ -0,0 +1,2 @@
pub(crate) mod tile;
pub(crate) mod tile_map;
33 changes: 33 additions & 0 deletions board_plugin/src/resources/tile.rs
@@ -0,0 +1,33 @@
use std::fmt::format;

#[cfg(feature = "debug")]
use colored::Colorize;

#[derive(Debug,Clone, Copy, PartialEq, Eq)]
pub enum Tile {
Bomb,
BombNeighbour(u8),
Empty,
}

impl Tile {
pub const fn is_bomb(&self) -> bool {
matches!(self, Self::Bomb)
}

pub fn console_output(&self) -> String {
format!(
"{}",
match self {
Tile::Bomb => "*".bright_red(),
Tile::BombNeighbour(bombs_count) => match bombs_count {
1 => "1".cyan(),
2 => "2".green(),
3 => "3".yellow(),
_ => bombs_count.to_string().red()
},
Tile::Empty => " ".black(),
}
)
}
}
150 changes: 150 additions & 0 deletions board_plugin/src/resources/tile_map.rs
@@ -0,0 +1,150 @@
use crate::components::Coordinates;
use crate::resources::tile::Tile;

use std::ops::{Deref, DerefMut};

use rand::{thread_rng, Rng};

// Delta coordinates for all 8 square neighbors
// [column, row]
const SQUARE_COORDINATES: [(i8, i8); 8] = [
(-1, -1), // Bottom left
(0, -1), // Bottom
(1, -1), // Bottom right
(-1, 0), // Left
(1, 0), // Right
(-1, 1), // Top Left
(0, 1), // Top
(1, 1), // Top Right
];

#[derive(Debug, Clone)]
pub struct TileMap {
bomb_count: u16,
height: u16,
width: u16,
map: Vec<Vec<Tile>>,
}

impl TileMap {
pub fn empty(width: u16, height: u16) -> Self {
let map = (0..height)
.into_iter()
.map(|_| (0..width).into_iter().map(|_| Tile::Empty).collect())
.collect();
Self {
bomb_count: 9,
height,
width,
map,
}
}

#[cfg(feature = "debug")]
pub fn console_output(&self) -> String {
let mut buffer = format!(
"Map: ({},{}) with {} bombs:\n",
self.width, self.height, self.bomb_count
);

let table_separator: String = (0..=(self.width + 1)).into_iter().map(|_| '-').collect();
buffer = format!("{}{}\n", buffer, table_separator);

for line in self.iter().rev() {
buffer = format!("{}|", buffer);
for tile in line.iter() {
buffer = format!("{}{}", buffer, tile.console_output())
}
buffer = format!("{}|\n", buffer);
}

format!("{}{}", buffer, table_separator)
}

pub fn safe_square_at(&self, coordinates: Coordinates) -> impl Iterator<Item = Coordinates> {
SQUARE_COORDINATES
.iter()
.copied()
.map(move |tuple| coordinates + tuple)
}

pub fn is_bomb_at(&self, coordinates: Coordinates) -> bool {
if coordinates.x >= self.width || coordinates.y >= self.height {
return false;
}

self.map[coordinates.y as usize][coordinates.x as usize].is_bomb()
}

pub fn bomb_count_at(&self, coordinates: Coordinates) -> u8 {
if self.is_bomb_at(coordinates) {
return 0;
}

let res = self
.safe_square_at(coordinates)
.filter(|coord| self.is_bomb_at(*coord))
.count();

res as u8
}

pub fn width(&self) -> u16 {
self.width
}

pub fn height(&self) -> u16 {
self.height
}

pub fn bomb_count(&self) -> u16 {
self.bomb_count
}

pub fn set_bombs(&mut self, bomb_count: u16) {
self.bomb_count = bomb_count;
let mut remaining_bombs = bomb_count;
let mut rng = thread_rng();

while remaining_bombs > 0 {
let row = rng.gen_range(0..self.height) as usize;
let column = rng.gen_range(0..self.width) as usize;
if let Tile::Empty = self[row][column] {
self[row][column] = Tile::Bomb;
remaining_bombs -= 1;
}
}

for row in 0..self.height {
for col in 0..self.width {
let coords = Coordinates { y: row, x: col };

if self.is_bomb_at(coords) {
continue;
};

let bomb_count = self.bomb_count_at(coords);
if bomb_count == 0 {
continue;
}

let tile = &mut self[row as usize][col as usize];
*tile = Tile::BombNeighbour(bomb_count);
}
}
}
}

impl Deref for TileMap {
type Target = Vec<Vec<Tile>>;

fn deref(&self) -> &Self::Target {
&self.map
}
}

impl DerefMut for TileMap {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.map
}
}

0 comments on commit 45e742b

Please sign in to comment.