mirror of https://git.sr.ht/~garritfra/midgard
Garrit Franke
3 years ago
3 changed files with 1810 additions and 371 deletions
@ -1,162 +1,182 @@
|
||||
extern crate noise; |
||||
extern crate rand; |
||||
extern crate termion; |
||||
|
||||
use crate::noise::utils::NoiseMapBuilder; |
||||
use crate::termion::cursor::DetectCursorPos; |
||||
use noise::utils::PlaneMapBuilder; |
||||
use noise::Fbm; |
||||
use std::fmt::Debug; |
||||
use std::fmt::Display; |
||||
use std::io::{stdin, stdout, Write}; |
||||
use std::path::Path; |
||||
use termion::event::Key; |
||||
use termion::input::TermRead; |
||||
use termion::raw::IntoRawMode; |
||||
use termion::{clear, cursor, screen}; |
||||
|
||||
const MAP_SIZE_X: usize = 100; |
||||
const MAP_SIZE_Y: usize = 100; |
||||
|
||||
struct GOL { |
||||
screen: Box<dyn Write>, |
||||
player: Player, |
||||
map: Box<[[char; MAP_SIZE_X]; MAP_SIZE_Y]>, |
||||
// This is based on example 3, but adds in highlighting visible tiles.
|
||||
//
|
||||
// Comments that duplicate previous examples have been removed for brevity.
|
||||
//////////////////////////////////////////////////////////////
|
||||
|
||||
rltk::add_wasm_support!(); |
||||
use rltk::prelude::*; |
||||
|
||||
#[derive(PartialEq, Copy, Clone)] |
||||
enum TileType { |
||||
Wall, |
||||
Floor, |
||||
} |
||||
|
||||
#[derive(PartialEq, Eq, Hash, Clone, Copy, Debug)] |
||||
pub struct Player { |
||||
pub pos: Pos, |
||||
// Just like example 3, but we're adding an additional vector: visible
|
||||
struct State { |
||||
map: Vec<TileType>, |
||||
player_position: usize, |
||||
visible: Vec<bool>, |
||||
} |
||||
|
||||
#[derive(PartialEq, Eq, Hash, Clone, Copy, Debug)] |
||||
pub struct Pos { |
||||
pub x: usize, |
||||
pub y: usize, |
||||
} |
||||
|
||||
impl GOL { |
||||
pub fn new() -> Self { |
||||
let screen = screen::AlternateScreen::from(stdout().into_raw_mode().unwrap()); |
||||
|
||||
let fbm = Fbm::new(); |
||||
impl State { |
||||
pub fn new() -> State { |
||||
// Same as example 3, but we've added the visible tiles
|
||||
let mut state = State { |
||||
map: vec![TileType::Floor; 80 * 50], |
||||
player_position: (25 * 80) + 40, // Equivalent to point2d_to_index(40, 25) but we haven't initialized it yet
|
||||
visible: vec![false; 80 * 50], |
||||
}; |
||||
|
||||
let noise_map = PlaneMapBuilder::new(&fbm) |
||||
.set_size(MAP_SIZE_X, MAP_SIZE_Y) |
||||
.set_x_bounds(-5.0, 5.0) |
||||
.set_y_bounds(-5.0, 5.0) |
||||
.build(); |
||||
for x in 0..80 { |
||||
let wall1_pos = state.point2d_to_index(Point::new(x, 0)); |
||||
let wall2_pos = state.point2d_to_index(Point::new(x, 49)); |
||||
state.map[wall1_pos] = TileType::Wall; |
||||
state.map[wall2_pos] = TileType::Wall; |
||||
} |
||||
for y in 0..50 { |
||||
let wall1_pos = state.point2d_to_index(Point::new(0, y)); |
||||
let wall2_pos = state.point2d_to_index(Point::new(79, y)); |
||||
state.map[wall1_pos] = TileType::Wall; |
||||
state.map[wall2_pos] = TileType::Wall; |
||||
} |
||||
|
||||
let mut map: Box<[[char; MAP_SIZE_X]; MAP_SIZE_Y]> = |
||||
Box::new([[' '; MAP_SIZE_X]; MAP_SIZE_Y]); |
||||
let mut rng = RandomNumberGenerator::new(); |
||||
|
||||
for (i, row) in map.clone().iter().enumerate() { |
||||
for (j, _) in row.iter().enumerate() { |
||||
map[i][j] = match noise_map.get_value(i, j) { |
||||
v if v > 0.0 => '#', |
||||
_ => ' ', |
||||
} |
||||
for _ in 0..400 { |
||||
let x = rng.range(1, 79); |
||||
let y = rng.range(1, 49); |
||||
let idx = state.point2d_to_index(Point::new(x, y)); |
||||
if state.player_position != idx { |
||||
state.map[idx] = TileType::Wall; |
||||
} |
||||
} |
||||
let mut s = Self { |
||||
screen: Box::new(screen), |
||||
player: Player { |
||||
pos: Pos { |
||||
x: MAP_SIZE_X / 2, |
||||
y: MAP_SIZE_Y / 2, |
||||
}, |
||||
}, |
||||
map: map, |
||||
}; |
||||
|
||||
s.write(&termion::clear::All); |
||||
s.write(&termion::cursor::Goto(1, 1)); |
||||
|
||||
log(format!("{:?}", s.player)); |
||||
s.flush(); |
||||
|
||||
s |
||||
state |
||||
} |
||||
|
||||
pub fn write(&mut self, w: &dyn Display) { |
||||
log(w); |
||||
write!(self.screen, "{}", w); |
||||
pub fn move_player(&mut self, delta: Point) { |
||||
let current_position = self.index_to_point2d(self.player_position); |
||||
let new_position = current_position + delta; |
||||
let new_idx = self.point2d_to_index(new_position); |
||||
if self.map[new_idx] == TileType::Floor { |
||||
self.player_position = new_idx; |
||||
} |
||||
} |
||||
} |
||||
|
||||
pub fn pos(&mut self) -> (u16, u16) { |
||||
return self.screen.cursor_pos().unwrap(); |
||||
} |
||||
// Implement the game loop
|
||||
impl GameState for State { |
||||
#[allow(non_snake_case)] |
||||
fn tick(&mut self, ctx: &mut Rltk) { |
||||
match ctx.key { |
||||
None => {} // Nothing happened
|
||||
Some(key) => { |
||||
// A key is pressed or held
|
||||
match key { |
||||
// Numpad
|
||||
VirtualKeyCode::Numpad8 => self.move_player(Point::new(0, -1)), |
||||
VirtualKeyCode::Numpad4 => self.move_player(Point::new(-1, 0)), |
||||
VirtualKeyCode::Numpad6 => self.move_player(Point::new(1, 0)), |
||||
VirtualKeyCode::Numpad2 => self.move_player(Point::new(0, 1)), |
||||
|
||||
// Numpad diagonals
|
||||
VirtualKeyCode::Numpad7 => self.move_player(Point::new(-1, -1)), |
||||
VirtualKeyCode::Numpad9 => self.move_player(Point::new(1, -1)), |
||||
VirtualKeyCode::Numpad1 => self.move_player(Point::new(-1, 1)), |
||||
VirtualKeyCode::Numpad3 => self.move_player(Point::new(1, 1)), |
||||
|
||||
// Cursors
|
||||
VirtualKeyCode::Up => self.move_player(Point::new(0, -1)), |
||||
VirtualKeyCode::Down => self.move_player(Point::new(0, 1)), |
||||
VirtualKeyCode::Left => self.move_player(Point::new(-1, 0)), |
||||
VirtualKeyCode::Right => self.move_player(Point::new(1, 0)), |
||||
|
||||
_ => {} // Ignore all the other possibilities
|
||||
} |
||||
} |
||||
} |
||||
|
||||
pub fn flush(&mut self) { |
||||
self.screen.flush().unwrap(); |
||||
} |
||||
} |
||||
// Set all tiles to not visible
|
||||
for v in self.visible.iter_mut() { |
||||
*v = false; |
||||
} |
||||
|
||||
fn log(x: impl Display) { |
||||
std::fs::File::create(Path::new("log")).unwrap(); |
||||
let mut file = std::fs::OpenOptions::new() |
||||
.write(true) |
||||
.append(true) |
||||
.open("./log") |
||||
.unwrap(); |
||||
// Obtain the player's visible tile set, and apply it
|
||||
let player_position = self.index_to_point2d(self.player_position); |
||||
let fov = rltk::field_of_view_set(player_position, 8, self); |
||||
|
||||
if let Err(e) = writeln!(file, "{}", x) { |
||||
eprintln!("Couldn't write to file: {}", e); |
||||
} |
||||
} |
||||
// Note that the steps above would generally not be run every frame!
|
||||
for idx in fov.iter() { |
||||
let point = self.point2d_to_index(*idx); |
||||
self.visible[point] = true; |
||||
} |
||||
|
||||
fn main() { |
||||
let stdin = stdin(); |
||||
let mut gol = GOL::new(); |
||||
gol.write(&termion::cursor::Hide); |
||||
// Clear the screen
|
||||
ctx.cls(); |
||||
|
||||
gol.flush(); |
||||
// Iterate the map array, incrementing coordinates as we go.
|
||||
let mut y = 0; |
||||
let mut x = 0; |
||||
for (i, tile) in self.map.iter().enumerate() { |
||||
// Render a tile depending upon the tile type; now we check visibility as well!
|
||||
let mut fg; |
||||
let mut glyph = "."; |
||||
|
||||
draw(&mut gol); |
||||
match tile { |
||||
TileType::Floor => { |
||||
fg = RGB::from_f32(0.5, 0.5, 0.0); |
||||
} |
||||
TileType::Wall => { |
||||
fg = RGB::from_f32(0.0, 1.0, 0.0); |
||||
glyph = "#"; |
||||
} |
||||
} |
||||
if !self.visible[i] { |
||||
fg = fg.to_greyscale(); |
||||
} |
||||
ctx.print_color(x, y, fg, RGB::from_f32(0., 0., 0.), glyph); |
||||
|
||||
for c in stdin.keys() { |
||||
let key = c.unwrap(); |
||||
if let Err(_) = update(&mut gol, &key) { |
||||
break; |
||||
// Move the coordinates
|
||||
x += 1; |
||||
if x > 79 { |
||||
x = 0; |
||||
y += 1; |
||||
} |
||||
} |
||||
draw(&mut gol); |
||||
|
||||
gol.flush(); |
||||
// Render the player @ symbol
|
||||
let ppos = self.index_to_point2d(self.player_position); |
||||
ctx.print_color( |
||||
ppos.x, |
||||
ppos.y, |
||||
RGB::from_f32(1.0, 1.0, 0.0), |
||||
RGB::from_f32(0., 0., 0.), |
||||
"@", |
||||
); |
||||
} |
||||
gol.write(&termion::cursor::Show); |
||||
} |
||||
|
||||
fn update(game: &mut GOL, key: &Key) -> Result<(), ()> { |
||||
match key { |
||||
Key::Left => game.player.pos.x -= 1, |
||||
Key::Right => game.player.pos.x += 1, |
||||
Key::Up => game.player.pos.y -= 1, |
||||
Key::Down => game.player.pos.y += 1, |
||||
Key::Char('q') => return Err(()), |
||||
_ => {} |
||||
}; |
||||
|
||||
Ok(()) |
||||
// To work with RLTK's algorithm features, we need to implement some the Algorithm2D trait for our map.
|
||||
|
||||
// First, default implementations of some we aren't using yet (more on these later!)
|
||||
impl BaseMap for State { |
||||
// We'll use this one - if its a wall, we can't see through it
|
||||
fn is_opaque(&self, idx: usize) -> bool { |
||||
self.map[idx as usize] == TileType::Wall |
||||
} |
||||
} |
||||
|
||||
fn draw(game: &mut GOL) { |
||||
game.write(&clear::All); |
||||
let size = termion::terminal_size().unwrap(); |
||||
let center_x = size.0 / 2; |
||||
let center_y = size.1 / 2; |
||||
game.write(&cursor::Goto(center_x, center_y)); |
||||
game.write(&'X'); |
||||
for col in 1..size.0 { |
||||
for row in 1..size.1 { |
||||
let cell_x = game.player.pos.x + row as usize; |
||||
let cell_y = game.player.pos.y + col as usize; |
||||
if let Some(r) = game.map.clone().get_mut(cell_x) { |
||||
if let Some(cell) = r.get_mut(cell_y) { |
||||
game.write(&cursor::Goto(row, col)); |
||||
game.write(&cell); |
||||
} |
||||
} |
||||
} |
||||
impl Algorithm2D for State { |
||||
fn dimensions(&self) -> Point { |
||||
Point::new(80, 50) |
||||
} |
||||
game.flush(); |
||||
} |
||||
|
||||
fn main() -> RltkError { |
||||
let context = RltkBuilder::simple80x50() |
||||
.with_title("RLTK Example 4 - Field-Of-View") |
||||
.build()?; |
||||
let gs = State::new(); |
||||
rltk::main_loop(context, gs) |
||||
} |
||||
|
Loading…
Reference in new issue