Compare commits
3 Commits
167ab4f5b8
...
18dcff18b1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
18dcff18b1 | ||
|
|
50f2645093 | ||
|
|
282d41ceb4 |
1
.gitignore
vendored
1
.gitignore
vendored
@ -1 +1,2 @@
|
|||||||
target
|
target
|
||||||
|
*.swp
|
||||||
|
|||||||
2
activate
Normal file
2
activate
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
export PATH=$PATH:/Users/paul/Source/cdungeon/target/debug
|
||||||
|
alias cdg='cd $(cdungeon)'
|
||||||
@ -40,3 +40,10 @@ Single-char
|
|||||||
⎕ Music ⎕ something
|
⎕ Music ⎕ something
|
||||||
|
|
||||||
|
|
||||||
|
was wäre mit
|
||||||
|
- seitwärts gehen
|
||||||
|
- mehr räume anzeigen
|
||||||
|
- aktuellen raum live updaten
|
||||||
|
- beep boops/animation wenn irgendwas im aktuellen raum passiert
|
||||||
|
- position beim hochgehen merken
|
||||||
|
- hintergrund rendern
|
||||||
|
|||||||
125
src/game.rs
125
src/game.rs
@ -1,13 +1,11 @@
|
|||||||
use std::{fs, path::PathBuf};
|
use std::{
|
||||||
|
error::Error, ffi::{OsString}, fmt::{self}, fs, path::PathBuf, process::Command
|
||||||
|
};
|
||||||
|
|
||||||
|
use color_eyre::eyre::Result;
|
||||||
use crossterm::event::{Event, KeyCode};
|
use crossterm::event::{Event, KeyCode};
|
||||||
use ratatui::{
|
use ratatui::{
|
||||||
Frame,
|
buffer::{Buffer, Cell}, layout::{Constraint, Layout, Rect}, prelude::Backend, style::Color, text::{Line, Text}, widgets::{Block, Widget}, Frame, Terminal
|
||||||
buffer::{Buffer, Cell},
|
|
||||||
layout::{Constraint, Layout, Rect},
|
|
||||||
style::Color,
|
|
||||||
text::{Line, Text},
|
|
||||||
widgets::{Block, Widget},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::geometry::{Grid, P2, V2};
|
use crate::geometry::{Grid, P2, V2};
|
||||||
@ -18,7 +16,7 @@ pub struct GameModel {
|
|||||||
room: Room,
|
room: Room,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone, Debug)]
|
||||||
pub enum GameEvent {
|
pub enum GameEvent {
|
||||||
Quit,
|
Quit,
|
||||||
Nop,
|
Nop,
|
||||||
@ -26,9 +24,10 @@ pub enum GameEvent {
|
|||||||
PlayerDash(V2<isize>),
|
PlayerDash(V2<isize>),
|
||||||
Interact,
|
Interact,
|
||||||
Navigate(NavigationTarget),
|
Navigate(NavigationTarget),
|
||||||
|
Run(OsString, Vec<OsString>),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone, Debug)]
|
||||||
pub enum NavigationTarget {
|
pub enum NavigationTarget {
|
||||||
Path(PathBuf),
|
Path(PathBuf),
|
||||||
Parent,
|
Parent,
|
||||||
@ -62,7 +61,7 @@ struct Room {
|
|||||||
tiles: Grid<Tile>,
|
tiles: Grid<Tile>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone, Debug)]
|
||||||
struct Tile {
|
struct Tile {
|
||||||
style: TileStyle,
|
style: TileStyle,
|
||||||
action: Option<GameEvent>,
|
action: Option<GameEvent>,
|
||||||
@ -95,21 +94,40 @@ impl Tile {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub enum UpdateResult {
|
||||||
|
ShouldExit(PathBuf),
|
||||||
|
NewModel(GameModel),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct GameError {}
|
||||||
|
|
||||||
|
impl fmt::Display for GameError {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(f, "ouch game broken")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Error for GameError {}
|
||||||
|
|
||||||
impl GameModel {
|
impl GameModel {
|
||||||
pub fn new(path: PathBuf) -> Self {
|
pub fn new(path: PathBuf) -> Result<Self> {
|
||||||
let path = path.canonicalize().unwrap();
|
let path = path.canonicalize()?;
|
||||||
let mut entries: Vec<PathBuf> = fs::read_dir(&path)
|
let mut entries: Vec<PathBuf> = fs::read_dir(&path)?
|
||||||
.unwrap()
|
.filter_map(|entry| entry.ok().map(|entry| entry.path()))
|
||||||
.map(|entry| entry.unwrap().path())
|
|
||||||
.collect();
|
.collect();
|
||||||
entries.sort();
|
entries.sort();
|
||||||
let max_entry_length = entries
|
let max_entry_length = entries
|
||||||
.iter()
|
.iter()
|
||||||
.map(|entry| entry.file_name().unwrap().to_string_lossy().chars().count())
|
.filter_map(|entry| {
|
||||||
|
entry
|
||||||
|
.file_name()
|
||||||
|
.map(|entry| entry.to_string_lossy().chars().count())
|
||||||
|
})
|
||||||
.max()
|
.max()
|
||||||
.unwrap_or(0);
|
.unwrap_or(0);
|
||||||
|
|
||||||
Self {
|
Ok(Self {
|
||||||
player_pos: P2::new(10, 2),
|
player_pos: P2::new(10, 2),
|
||||||
room: Room {
|
room: Room {
|
||||||
tiles: {
|
tiles: {
|
||||||
@ -148,12 +166,14 @@ impl GameModel {
|
|||||||
} else {
|
} else {
|
||||||
tiles
|
tiles
|
||||||
.get_mut(P2::new(10, 2 + i as isize))
|
.get_mut(P2::new(10, 2 + i as isize))
|
||||||
.map(|tile| *tile = Tile::new(TileStyle::Box));
|
.map(|tile| *tile = Tile::new(TileStyle::Box).with_action(GameEvent::Run("vim".into(), vec![
|
||||||
|
entry.into()
|
||||||
|
])));
|
||||||
}
|
}
|
||||||
|
|
||||||
for (j, c) in entry
|
for (j, c) in entry
|
||||||
.file_name()
|
.file_name()
|
||||||
.unwrap()
|
.ok_or(GameError {})?
|
||||||
.to_string_lossy()
|
.to_string_lossy()
|
||||||
.chars()
|
.chars()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
@ -169,25 +189,26 @@ impl GameModel {
|
|||||||
},
|
},
|
||||||
|
|
||||||
path,
|
path,
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update(self, event: Event) -> Result<Self, PathBuf> {
|
pub fn update<T: Backend>(self, event: Event, terminal: &mut Terminal<T>) -> Result<UpdateResult> {
|
||||||
self.update_game(GameEvent::from_crossterm(event))
|
self.update_game(GameEvent::from_crossterm(event), terminal)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_game(self, event: GameEvent) -> Result<Self, PathBuf> {
|
fn update_game<T: Backend>(self, event: GameEvent, terminal: &mut Terminal<T>) -> Result<UpdateResult> {
|
||||||
match event {
|
use UpdateResult::*;
|
||||||
GameEvent::Quit => Err(self.path),
|
Ok(match event {
|
||||||
GameEvent::Nop => Ok(self),
|
GameEvent::Quit => ShouldExit(self.path),
|
||||||
|
GameEvent::Nop => NewModel(self),
|
||||||
GameEvent::PlayerMove(direction) => {
|
GameEvent::PlayerMove(direction) => {
|
||||||
let player_pos = self.player_pos + direction;
|
let player_pos = self.player_pos + direction;
|
||||||
if let Some(target_tile) = self.room.tiles.get(player_pos)
|
if let Some(target_tile) = self.room.tiles.get(player_pos)
|
||||||
&& target_tile.may_enter()
|
&& target_tile.may_enter()
|
||||||
{
|
{
|
||||||
Ok(Self { player_pos, ..self })
|
NewModel(Self { player_pos, ..self })
|
||||||
} else {
|
} else {
|
||||||
Ok(self)
|
NewModel(self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
GameEvent::PlayerDash(direction) => {
|
GameEvent::PlayerDash(direction) => {
|
||||||
@ -206,7 +227,7 @@ impl GameModel {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(Self { player_pos, ..self })
|
NewModel(Self { player_pos, ..self })
|
||||||
}
|
}
|
||||||
GameEvent::Interact => {
|
GameEvent::Interact => {
|
||||||
let opt_action = self
|
let opt_action = self
|
||||||
@ -216,27 +237,36 @@ impl GameModel {
|
|||||||
.and_then(|tile| tile.action.clone());
|
.and_then(|tile| tile.action.clone());
|
||||||
|
|
||||||
if let Some(action) = opt_action {
|
if let Some(action) = opt_action {
|
||||||
self.update_game(action)
|
self.update_game(action, terminal)?
|
||||||
} else {
|
} else {
|
||||||
Ok(self)
|
NewModel(self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
GameEvent::Navigate(target) => {
|
GameEvent::Navigate(target) => {
|
||||||
let path = match target {
|
let path = match target {
|
||||||
NavigationTarget::Path(path) => path,
|
NavigationTarget::Path(path) => path,
|
||||||
NavigationTarget::Parent => self.path.parent().unwrap().to_path_buf(),
|
NavigationTarget::Parent => {
|
||||||
|
self.path.parent().ok_or(GameError {})?.to_path_buf()
|
||||||
|
}
|
||||||
};
|
};
|
||||||
Ok(Self::new(path))
|
|
||||||
|
NewModel(Self::new(path)?)
|
||||||
}
|
}
|
||||||
}
|
GameEvent::Run(cmd, args) => {
|
||||||
|
Command::new(&cmd).args(args).spawn().unwrap().wait().unwrap();
|
||||||
|
// TODO: Figure out why terminal isn't cleared on exit.
|
||||||
|
terminal.clear().unwrap();
|
||||||
|
NewModel(self)
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn render(&self, frame: &mut Frame) {
|
pub fn render(&self, frame: &mut Frame) {
|
||||||
let layout =
|
let layout =
|
||||||
Layout::vertical([Constraint::Fill(1), Constraint::Length(3)]).split(frame.area());
|
Layout::vertical([Constraint::Fill(1), Constraint::Length(4)]).split(frame.area());
|
||||||
|
|
||||||
let camera_block_area = layout[0];
|
let camera_block_area = layout[0];
|
||||||
let info_block_area = layout[1];
|
let bottom_bar_area = layout[1];
|
||||||
|
|
||||||
let camera_block = Block::bordered()
|
let camera_block = Block::bordered()
|
||||||
.title("camera")
|
.title("camera")
|
||||||
@ -244,15 +274,30 @@ impl GameModel {
|
|||||||
let camera_area = camera_block.inner(camera_block_area);
|
let camera_area = camera_block.inner(camera_block_area);
|
||||||
let camera = CameraWidget::new(self);
|
let camera = CameraWidget::new(self);
|
||||||
|
|
||||||
|
let bottom_bar_layout = Layout::horizontal([
|
||||||
|
Constraint::Fill(1),
|
||||||
|
Constraint::Fill(1),
|
||||||
|
]).split(bottom_bar_area);
|
||||||
|
|
||||||
|
let info_block_area = bottom_bar_layout[0];
|
||||||
|
let debug_block_area = bottom_bar_layout[1];
|
||||||
|
|
||||||
let info_block = Block::bordered().title("cwd");
|
let info_block = Block::bordered().title("cwd");
|
||||||
let info_area = info_block.inner(info_block_area);
|
let info_area = info_block.inner(info_block_area);
|
||||||
let info = Text::raw(self.path.to_string_lossy());
|
let info = Text::raw(self.path.to_string_lossy());
|
||||||
|
|
||||||
|
let debug_block = Block::bordered().title("debug");
|
||||||
|
let debug_area = info_block.inner(debug_block_area);
|
||||||
|
let debug = Text::raw(self.room.tiles.get(self.player_pos).map(|tile| format!("{:?}\n{:?}", tile.style, tile.action)).unwrap_or("".to_string()));
|
||||||
|
|
||||||
frame.render_widget(camera_block, camera_block_area);
|
frame.render_widget(camera_block, camera_block_area);
|
||||||
frame.render_widget(camera, camera_area);
|
frame.render_widget(camera, camera_area);
|
||||||
|
|
||||||
frame.render_widget(info_block, info_block_area);
|
frame.render_widget(info_block, info_block_area);
|
||||||
frame.render_widget(info, info_area);
|
frame.render_widget(info, info_area);
|
||||||
|
|
||||||
|
frame.render_widget(debug_block, debug_block_area);
|
||||||
|
frame.render_widget(debug, debug_area);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_tiles(&self) -> Grid<TileStyle> {
|
fn render_tiles(&self) -> Grid<TileStyle> {
|
||||||
@ -260,7 +305,7 @@ impl GameModel {
|
|||||||
self.room
|
self.room
|
||||||
.tiles
|
.tiles
|
||||||
.get(P2::new(x as isize, y as isize))
|
.get(P2::new(x as isize, y as isize))
|
||||||
.unwrap()
|
.expect("programmer error: copying a grid shouldnt yield OOB errors")
|
||||||
.style
|
.style
|
||||||
.clone()
|
.clone()
|
||||||
});
|
});
|
||||||
@ -273,7 +318,7 @@ impl GameModel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone, Debug)]
|
||||||
enum TileStyle {
|
enum TileStyle {
|
||||||
Floor,
|
Floor,
|
||||||
Player,
|
Player,
|
||||||
@ -283,12 +328,12 @@ enum TileStyle {
|
|||||||
Box,
|
Box,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone, Debug)]
|
||||||
struct Portal {
|
struct Portal {
|
||||||
kind: PortalKind,
|
kind: PortalKind,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone, Debug)]
|
||||||
enum PortalKind {
|
enum PortalKind {
|
||||||
Ladder,
|
Ladder,
|
||||||
Portal,
|
Portal,
|
||||||
|
|||||||
@ -104,4 +104,4 @@ impl<T> Grid<T> {
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
15
src/main.rs
15
src/main.rs
@ -4,13 +4,18 @@ mod geometry;
|
|||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use color_eyre::eyre::Result;
|
use color_eyre::eyre::Result;
|
||||||
use crossterm::{event::{self, DisableMouseCapture, EnableMouseCapture}, execute, terminal::{EnterAlternateScreen, LeaveAlternateScreen, disable_raw_mode, enable_raw_mode}};
|
use crossterm::{
|
||||||
|
event::{self, DisableMouseCapture, EnableMouseCapture},
|
||||||
|
execute,
|
||||||
|
terminal::{EnterAlternateScreen, LeaveAlternateScreen, disable_raw_mode, enable_raw_mode},
|
||||||
|
};
|
||||||
use ratatui::{
|
use ratatui::{
|
||||||
Terminal,
|
Terminal,
|
||||||
prelude::{Backend, CrosstermBackend},
|
prelude::{Backend, CrosstermBackend},
|
||||||
};
|
};
|
||||||
|
|
||||||
use game::GameModel;
|
use game::GameModel;
|
||||||
|
use game::UpdateResult;
|
||||||
|
|
||||||
fn main() -> Result<()> {
|
fn main() -> Result<()> {
|
||||||
enable_raw_mode()?;
|
enable_raw_mode()?;
|
||||||
@ -33,14 +38,14 @@ fn main() -> Result<()> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn main_loop<T: Backend>(terminal: &mut Terminal<T>) -> Result<PathBuf> {
|
fn main_loop<T: Backend>(terminal: &mut Terminal<T>) -> Result<PathBuf> {
|
||||||
let mut model = GameModel::new(PathBuf::from("."));
|
let mut model = GameModel::new(PathBuf::from("."))?;
|
||||||
loop {
|
loop {
|
||||||
terminal.draw(|frame| model.render(frame))?;
|
terminal.draw(|frame| model.render(frame))?;
|
||||||
|
|
||||||
let term_event = event::read()?;
|
let term_event = event::read()?;
|
||||||
match model.update(term_event) {
|
match model.update(term_event, terminal)? {
|
||||||
Ok(new_model) => model = new_model,
|
UpdateResult::NewModel(new_model) => model = new_model,
|
||||||
Err(path) => break Ok(path),
|
UpdateResult::ShouldExit(path) => break Ok(path),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user