7 Hour Roguelike, spring 2022
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

268 lines
7.1 KiB

use super::*;
use std::fmt::Debug;
use std::io::{self, Write};
use rogue_util::coord::*;
use rogue_util::grid::region::Region;
#[derive(Debug)]
pub struct Neighbors<'a, T: Debug> {
ul: Option<&'a T>, u: Option<&'a T>, ur: Option<&'a T>,
l: Option<&'a T>, this: Option<&'a T>, r: Option<&'a T>,
dl: Option<&'a T>, d: Option<&'a T>, dr: Option<&'a T>,
}
impl<'a, T: Debug + Default> Neighbors<'a, T> {
pub fn from_region_pos<'r: 'a>(r: &'r Region<T>, p: V2i, _def: &'a T) -> Neighbors<'a, T> {
Neighbors {
ul: r.get(p + V2i(-1, -1)),
u: r.get(p + V2i(0, -1)),
ur: r.get(p + V2i(1, -1)),
l: r.get(p + V2i(-1, 0)),
this: r.get(p),
r: r.get(p + V2i(1, 0)),
dl: r.get(p + V2i(-1, 1)),
d: r.get(p + V2i(0, 1)),
dr: r.get(p + V2i(1, 1)),
}
}
}
impl<'a, T: Debug> Neighbors<'a, T> {
pub fn for_each<F>(&self, mut f: F)
where
F: FnMut(Option<&'a T>)
{
f(self.ul); f(self.u); f(self.ur);
f(self.l); f(self.this); f(self.r);
f(self.dl); f(self.d); f(self.dr);
}
}
pub trait Renderer {
type Tile: Debug;
fn render<'a, 's: 'a>(&'s mut self, neighbors: Neighbors<'a, Self::Tile>) -> u8;
}
pub struct BasicRenderer;
impl Renderer for BasicRenderer {
type Tile = Tile;
fn render<'a, 's: 'a>(&'s mut self, neighbors: Neighbors<'a, Tile>) -> u8 {
match neighbors.this.map(|t| t.content) {
Some(Content::Air) => b'.',
Some(Content::Stone) | None => b' ',
}
}
}
pub struct InvertRenderer;
impl Renderer for InvertRenderer {
type Tile = Tile;
fn render<'a, 's: 'a>(&'s mut self, neighbors: Neighbors<'a, Tile>) -> u8 {
match neighbors.this.map(|t| t.content) {
Some(Content::Air) => b' ',
Some(Content::Stone) | None => b'#',
}
}
}
pub struct EdgeRenderer;
impl Renderer for EdgeRenderer {
type Tile = Tile;
fn render<'a, 's: 'a>(&'s mut self, neighbors: Neighbors<'a, Tile>) -> u8 {
match neighbors.this.map(|t| t.content) {
Some(Content::Air) => b'.',
None => b' ',
Some(Content::Stone) => {
let mut air_count = 0;
neighbors.for_each(
|t| if let Some(Tile { content: Content::Air, .. }) = t { air_count += 1; });
if air_count > 0 {
b'#'
} else {
b' '
}
},
}
}
}
pub struct RenderState {
term_size: RefCell<V2i>,
center: V2i,
}
impl RenderState {
pub fn new_centered(term_size: V2i, center: V2i) -> RenderState {
debug_assert!(term_size.is_strict_q1());
RenderState {
term_size: RefCell::new(term_size),
center,
}
}
pub fn new(term_size: V2i) -> RenderState { RenderState::new_centered(term_size, V2i(0, 0)) }
pub fn center(&self) -> V2i { self.center }
pub fn term_size(&self) -> V2i { self.term_size.borrow().clone() }
pub fn refresh_term_size(&self) -> io::Result<()> { let (w, h) = termion::terminal_size()?; *self.term_size.borrow_mut() = V2i(w as isize, h as isize); Ok(()) }
pub fn recenter(&mut self, c: V2i) {
self.center = c;
}
pub fn screen_rect(&self) -> R2i {
let ts = self.term_size();
let halfsize = ts / V2i(2, 2);
R2i::origin_dim(self.center - halfsize, ts)
}
pub fn render<R, W>(&self, region: &Region<Tile>, renderer: &mut R, out: &mut W) -> io::Result<()>
where
R: Renderer<Tile=Tile>,
W: Write
{
self.refresh_term_size()?;
let screen = self.screen_rect();
write!(out, "{}{}",
termion::clear::All,
termion::cursor::Goto(1, 1),
)?;
let def_tile = Tile::default();
let mut screen_buffer: Vec<u8> = Vec::new();
for pt in &screen {
screen_buffer.push(renderer.render(Neighbors::from_region_pos(region, pt, &def_tile)));
}
write!(out, "{}", unsafe { core::str::from_utf8_unchecked(&screen_buffer) })?;
Ok(())
}
pub fn go_to_pt<W: Write>(&self, pt: V2i, out: &mut W) -> io::Result<()> {
let spt = pt - self.screen_rect().origin() + V2i(1, 1);
write!(out, "{}", termion::cursor::Goto(spt.0 as u16, spt.1 as u16))
}
pub fn draw_grid<W: Write>(&self, region: &Region<Tile>, out: &mut W) -> io::Result<()> {
let screen = self.screen_rect();
let gs = region.grid_size();
for pt in &screen {
if pt.0.rem_euclid(gs.0) == 0 || pt.1.rem_euclid(gs.1) == 0 {
self.go_to_pt(pt, out)?;
write!(out, "+")?;
}
}
Ok(())
}
pub fn draw_unpop<W: Write>(&self, region: &Region<Tile>, out: &mut W) -> io::Result<()> {
let screen = self.screen_rect();
for pt in &screen {
if !region.is_populated_tile(pt) {
self.go_to_pt(pt, out)?;
write!(out, "?")?;
}
}
Ok(())
}
}
#[cfg(test)]
mod test {
use super::*;
use rogue_util::grid::region::RegionConfig;
fn make_test_renderer() -> io::Result<RenderState> {
let (w, h) = termion::terminal_size()?;
Ok(RenderState::new(V2i(w as isize, h as isize)))
}
fn make_test_region() -> Region<Tile> {
RegionConfig::<Tile>::default().build().expect("Failed to make Region")
}
fn draw_line(rg: &mut Region<Tile>) {
let gs = rg.grid_size();
for pt in rogue_util::raster::line(V2i(3, 2), V2i(gs.0 - 1, gs.1 / 2)) {
rg.get_mut(pt).content = Content::Air;
}
}
#[test]
fn render_default_region() -> io::Result<()> {
let rs = make_test_renderer()?;
let rg = make_test_region();
let mut out = io::stdout();
rs.render(&rg, &mut BasicRenderer, &mut out)
}
#[test]
fn render_full_region() -> io::Result<()> {
let rs = make_test_renderer()?;
let mut rg = make_test_region();
for pt in &R2i::origin_dim(V2i(0, 0), rg.grid_size()) {
rg.get_mut(pt).content = Content::Air;
}
let mut out = io::stdout();
rs.render(&rg, &mut BasicRenderer, &mut out)
}
#[test]
fn render_line_region_basic() -> io::Result<()> {
let rs = make_test_renderer()?;
let mut rg = make_test_region();
draw_line(&mut rg);
let mut out = io::stdout();
rs.render(&rg, &mut BasicRenderer, &mut out)
}
#[test]
fn render_line_region_edge() -> io::Result<()> {
let rs = make_test_renderer()?;
let mut rg = make_test_region();
draw_line(&mut rg);
let mut out = io::stdout();
rs.render(&rg, &mut EdgeRenderer, &mut out)
}
#[test]
fn render_line_region_invert() -> io::Result<()> {
let rs = make_test_renderer()?;
let mut rg = make_test_region();
draw_line(&mut rg);
let mut out = io::stdout();
rs.render(&rg, &mut InvertRenderer, &mut out)
}
}