diff --git a/src/components.rs b/src/components.rs index 0b7f39a..bdc45ee 100644 --- a/src/components.rs +++ b/src/components.rs @@ -16,9 +16,12 @@ pub struct CharRender { pub glyph: char, } -/// Entities that the user can control using the keyboard. +/// Entities that users can control. #[derive(Component)] -pub struct Player; +pub struct Player { + /// The list of cells that are known to the player. + pub known_cells: Vec>, +} /// Entities that take turns periodically. #[derive(Component)] @@ -46,6 +49,18 @@ pub fn register_all(world: &mut World) { world.register::(); } +impl From<&Position> for (i32, i32) { + fn from(pos: &Position) -> Self { + (pos.x, pos.y) + } +} + +impl From<(i32, i32)> for Position { + fn from((x, y): (i32, i32)) -> Self { + Self { x, y } + } +} + /// An action that a mob can perform that takes up a turn. #[derive(Clone, Copy)] pub enum MobAction { diff --git a/src/level.rs b/src/level.rs index 43a3c30..1bfeb1b 100644 --- a/src/level.rs +++ b/src/level.rs @@ -4,7 +4,12 @@ use pancurses::Window; use rand::Rng; use specs::prelude::*; -use crate::{components::{CharRender, Position}, io::{Color, set_color}, rooms}; +use crate::{ + components::{CharRender, Position}, + io::{set_color, Color}, + rooms, + visibility::{visible, CellVisibility, Lighting}, +}; /// The size of a dungeon level, in tiles. pub const LEVEL_SIZE: (usize, usize) = (80, 24); @@ -39,6 +44,23 @@ pub enum DungeonTile { Downstair, } +/// A style for drawing a particular tile in the dungeon. +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum DrawStyle { + /// Don't draw the tile. (The player has never seen this tile + /// before, and is unaware of its contents.) + Undiscovered, + + /// Draw the tile in a darker color than normal. (The player has + /// seen this tile before and remembers its contents, but is not + /// actively looking at it.) + Discovered, + + /// Draw the tile in a normal color. (The player can see the tile + /// from where they are standing.) + Visible, +} + impl DungeonTile { /// Whether this tile is considered a floor tile, for the purposes /// of rendering walls. @@ -99,20 +121,22 @@ impl DungeonLevel { /// Draws a level on the display window. Draws only the cells for /// which `filter` returns true; use `|_| true` to draw the whole /// level. - pub fn draw(&self, win: &Window, filter: impl Fn((i32, i32)) -> bool) { + pub fn draw(&self, win: &Window, visibility: impl Fn((i32, i32)) -> DrawStyle) { for y in 0..LEVEL_SIZE.1 { win.mv(y as _, 0); for x in 0..LEVEL_SIZE.0 { - if (x + y) % 2 == 0 { - set_color(win, Color::Yellow); - } else { - set_color(win, Color::Red); - } - - win.addch(if filter((x as _, y as _)) { - self.render_tile(x, y) - } else { - ' ' + win.addch(match visibility((x as _, y as _)) { + DrawStyle::Undiscovered => ' ', + DrawStyle::Discovered => { + // Using red as a placeholder; black doesn't + // seem to work rn(?) + set_color(win, Color::Red); + self.render_tile(x, y) + } + DrawStyle::Visible => { + set_color(win, Color::White); + self.render_tile(x, y) + } }); } } @@ -168,6 +192,25 @@ impl DungeonLevel { pub fn tile(&self, x: i32, y: i32) -> &DungeonTile { &self.tiles[y as usize][x as usize] } + + /// Whether a monster standing at `from` can see the contents of cell + /// `to`. + pub fn can_see(&self, from: (i32, i32), to: (i32, i32)) -> bool { + visible( + from, + to, + Some(10), + |(x, y)| { + if self.tile(x, y).is_navigable() { + CellVisibility::Transparent + } else { + CellVisibility::Blocking + } + }, + // Level is fully lit for now. + |(_x, _y)| Lighting::Lit, + ) + } } impl Display for DungeonLevel { diff --git a/src/main.rs b/src/main.rs index c882191..052174b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,11 +1,11 @@ use components::{register_all, CharRender, MobAction, Mobile, Player, Position, TurnTaker}; use io::init_window; -use level::DungeonLevel; +use level::{DungeonLevel, LEVEL_SIZE}; use player::player_turn; use rand::thread_rng; use specs::prelude::*; -use systems::{MobSystem, TimeSystem}; +use systems::{DiscoverySystem, MobSystem, TimeSystem}; mod components; mod io; @@ -28,12 +28,13 @@ fn main() { world .create_entity() - .with(Position { - x: spawn_pos.0, - y: spawn_pos.1, - }) + .with(Position::from(spawn_pos)) .with(CharRender { glyph: '@' }) - .with(Player) + .with(Player { + known_cells: (0..LEVEL_SIZE.1) + .map(|_| (0..LEVEL_SIZE.0).map(|_| false).collect()) + .collect(), + }) .with(Mobile { next_action: MobAction::Nop, }) @@ -46,6 +47,7 @@ fn main() { let mut dispatcher = DispatcherBuilder::new() .with(TimeSystem, "time", &[]) .with(MobSystem, "mobs", &[]) + .with(DiscoverySystem, "discovery", &[]) .build(); let mut window = match init_window() { @@ -53,7 +55,7 @@ fn main() { Err(err) => { println!("Error initializing window: {}", err); return; - }, + } }; loop { diff --git a/src/player.rs b/src/player.rs index 7a02814..bb5c91c 100644 --- a/src/player.rs +++ b/src/player.rs @@ -5,9 +5,8 @@ use specs::prelude::*; use crate::{ components::{CharRender, MobAction, Mobile, Player, Position}, - level::DungeonLevel, io::quit, - visibility::{visible, CellVisibility, Lighting}, + level::{DrawStyle, DungeonLevel}, }; /// Runs a player turn on the ECS, using the given `screen` for input @@ -96,21 +95,18 @@ fn render_screen(ecs: &mut World, screen: &mut Window) { // Draw the base level. let level = ecs.fetch::(); + let known_cells = &plrs.join().next().expect("Player must exist").known_cells; level.draw(screen, |cell| { - visible( - (player_pos.x, player_pos.y), - cell, - Some(10), - |(x, y)| { - if level.tile(x, y).is_navigable() { - CellVisibility::Transparent + match level.can_see(player_pos.into(), cell) { + true => DrawStyle::Visible, + false => { + if known_cells[cell.1 as usize][cell.0 as usize] { + DrawStyle::Discovered } else { - CellVisibility::Blocking + DrawStyle::Undiscovered } - }, - // Level is fully lit for now. - |(_x, _y)| Lighting::Lit, - ) + } + } }); // Draw all renderable entities. diff --git a/src/systems.rs b/src/systems.rs index c02e2ce..cc49eff 100644 --- a/src/systems.rs +++ b/src/systems.rs @@ -2,7 +2,10 @@ use specs::prelude::*; -use crate::components::{MobAction, Mobile, Position, TurnTaker}; +use crate::{ + components::{MobAction, Mobile, Player, Position, TurnTaker}, + level::DungeonLevel, +}; /// System for ticking the turn counter on every entity; this system /// implements the relationship between real-world time and in-game @@ -46,3 +49,26 @@ impl<'a> System<'a> for MobSystem { } } } + +/// System for updating player-discovered cells. +pub struct DiscoverySystem; + +impl<'a> System<'a> for DiscoverySystem { + type SystemData = ( + WriteStorage<'a, Player>, + ReadStorage<'a, Position>, + ReadExpect<'a, DungeonLevel>, + ); + + fn run(&mut self, (mut players, position, level): Self::SystemData) { + for (player, pos) in (&mut players, &position).join() { + for (y, row) in player.known_cells.iter_mut().enumerate() { + for (x, known) in row.iter_mut().enumerate() { + if level.can_see(pos.into(), (x as _, y as _)) { + *known = true; + } + } + } + } + } +} diff --git a/src/visibility.rs b/src/visibility.rs index dd79990..14cafd1 100644 --- a/src/visibility.rs +++ b/src/visibility.rs @@ -14,10 +14,9 @@ pub enum CellVisibility { /// How well-lit a cell is. #[derive(Debug, PartialEq)] pub enum Lighting { - /// Monsters can only see in this cell if the cell is immediately - /// adjacent to the monster. - Dark, - + // /// Monsters can only see in this cell if the cell is immediately + // /// adjacent to the monster. + // Dark, /// Monsters can see in this cell from far away. Lit, }