From aa32eaec30b052fd8fd4540f79c29587d6284fd6 Mon Sep 17 00:00:00 2001 From: Niko Abeler Date: Sat, 31 Dec 2022 15:22:44 +0100 Subject: [PATCH] people traveling --- src/app.rs | 17 ++++--- src/entity.rs | 5 +++ src/events/person_genesis.rs | 4 +- src/main.rs | 3 +- src/person.rs | 76 ++++++++++++++++++++++++++----- src/state.rs | 86 ++++++++++++++++++++++++++++++------ 6 files changed, 154 insertions(+), 37 deletions(-) create mode 100644 src/entity.rs diff --git a/src/app.rs b/src/app.rs index a62b250..8e31bd1 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1,9 +1,7 @@ -use std::{rc::Rc}; - use crossterm::event::{read, Event, KeyCode}; -use tui::{Terminal, backend::Backend, Frame, layout::Layout}; +use tui::{backend::Backend, Frame, layout::Layout}; -use crate::{state::{self, GameState}, person::Person}; +use crate::{state::GameState, person::Person, entity::Entity}; /** * |........................| @@ -17,7 +15,7 @@ use crate::{state::{self, GameState}, person::Person}; enum AppStatus { Initial, GuestSelection, - TalkToGuest(Rc), + TalkToGuest(Option), } pub struct App<'a> { @@ -68,7 +66,7 @@ impl<'a> App<'a> { KeyCode::Enter => { let selected = self.guest_state.selected().unwrap(); let guest = &self.state.guests()[selected]; - self.status = AppStatus::TalkToGuest(guest.clone()); + self.status = AppStatus::TalkToGuest(guest.id()); }, KeyCode::Char('.') => { self.state.step(); @@ -109,8 +107,8 @@ impl<'a> App<'a> { AppStatus::GuestSelection => { self.draw_guest_selection(f); }, - AppStatus::TalkToGuest(guest) => { - self.draw_talk_to_guest(f, guest.clone()); + AppStatus::TalkToGuest(guest_id) => { + self.draw_talk_to_guest(f, *guest_id); } } @@ -161,7 +159,8 @@ impl<'a> App<'a> { f.render_widget(controls, chunks[1]); } - fn draw_talk_to_guest(&self, f: &mut Frame, guest: Rc) { + fn draw_talk_to_guest(&self, f: &mut Frame, guest: Option) { + let guest = self.state.get_person(guest.unwrap()).unwrap(); let main_window = tui::widgets::Paragraph::new(guest.name.clone()) .block(tui::widgets::Block::default().title("Guests").borders(tui::widgets::Borders::ALL)) .style(tui::style::Style::default().fg(tui::style::Color::White)); diff --git a/src/entity.rs b/src/entity.rs new file mode 100644 index 0000000..5966059 --- /dev/null +++ b/src/entity.rs @@ -0,0 +1,5 @@ +pub trait Entity { + fn id(&self) -> Option; + fn set_id(&mut self, id: u32); +} + diff --git a/src/events/person_genesis.rs b/src/events/person_genesis.rs index 2146c9a..1b82e19 100644 --- a/src/events/person_genesis.rs +++ b/src/events/person_genesis.rs @@ -4,12 +4,12 @@ use crate::{state::{GameState, Effect}, world::Town, person::Person}; pub struct PersonGenesis { pub town: Rc, - pub person: Rc, + pub person: Person, } impl Effect for PersonGenesis { fn apply(&self, state: &mut GameState) { - state.add_person(self.person.clone()); + state.add_person(&mut self.person.clone()); } fn description(&self) -> String { diff --git a/src/main.rs b/src/main.rs index 3903c7e..2e548ef 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,6 +5,7 @@ mod time; mod generators; mod person; mod app; +mod entity; use app::App; use noise::{Perlin, ScalePoint, Add, NoiseFn, ScaleBias}; @@ -132,10 +133,10 @@ fn main() -> Result<(), io::Error> { let mut rng = rand::thread_rng(); for _ in 0..N_TOWNS { - world_state.step_n(rng.gen_range(0..360*3)); world_state.found_town(); } + world_state.build_tavern(); // setup terminal diff --git a/src/person.rs b/src/person.rs index efd08e4..c84dcd7 100644 --- a/src/person.rs +++ b/src/person.rs @@ -1,21 +1,24 @@ use std::{rc::Rc, fmt::Display}; -use rand::seq::SliceRandom; use rand::prelude::*; -use crate::{time::Time, world::Town, generators::PersonNameGenerator, state::GameState}; +use crate::{time::Time, world::Town, generators::PersonNameGenerator, state::GameState, entity::Entity}; +#[derive(Clone, Copy)] pub enum Profession { Peasant, Adventurer, Blacksmith, } +#[derive(Clone, Copy)] pub enum Agenda { - Idle, + Idle(u32), Traveling([usize; 2]), } +#[derive(Clone)] pub struct Person { + pub id: Option, pub name: String, pub birth_date: Time, pub birth_location: Rc, @@ -27,23 +30,28 @@ pub struct Person { impl Person { pub fn new(birth_date: Time, birth_location: Rc, location: [usize; 2]) -> Person { Person { + id: None, name: PersonNameGenerator::name(), birth_date: birth_date, birth_location: birth_location, profession: Profession::Peasant, - location: [0, 0], - agenda: Agenda::Idle, + location: location, + agenda: Agenda::Idle(0), } } pub fn step(&mut self, state: &mut GameState) { match &self.agenda { - Agenda::Idle => { + Agenda::Idle(days) => { // do nothing - // pick random destination - let mut rng = rand::thread_rng(); - let dest = state.world.structures.keys().choose(&mut rng); - self.agenda = Agenda::Traveling(*dest.unwrap()); + if *days <= 0 { + // pick random destination + let mut rng = rand::thread_rng(); + let dest = state.world.structures.keys().choose(&mut rng); + self.agenda = Agenda::Traveling(*dest.unwrap()); + } else { + self.agenda = Agenda::Idle(days - 1); + } }, Agenda::Traveling(destination) => { // TDOO: A* pathfinding with terrain costs @@ -59,7 +67,7 @@ impl Person { self.location[1] -= 1; } if self.location == *destination { - self.agenda = Agenda::Idle; + self.agenda = Agenda::Idle(10); } }, } @@ -70,4 +78,50 @@ impl Display for Person { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.name) } +} + +impl Entity for Person { + fn id(&self) -> Option { + self.id + } + + fn set_id(&mut self, id: u32) { + self.id = Some(id); + } +} + +#[cfg(test)] +mod tests { + + use crate::world::{World, Structure}; + + use super::*; + + #[test] + fn test_person_creation() { + let person = Person::new(Time { time: 0 }, Rc::new(Town::new()), [0, 0]); + assert_ne!(person.name, ""); + } + + #[test] + fn test_traveling() { + let mut person = Person::new(Time { time: 0 }, Rc::new(Town::new()), [0, 0]); + person.agenda = Agenda::Traveling([10, 0]); + person.step(&mut GameState::new(World::new(32))); + assert_eq!(person.location, [1, 0]); + } + + #[test] + fn test_start_traveling() { + let mut person = Person::new(Time { time: 0 }, Rc::new(Town::new()), [0, 0]); + person.agenda = Agenda::Idle(0); + let mut state = GameState::new(World::new(32)); + state.world.add_structure(10, 10, Structure::Town(Rc::new(Town::new()))); + person.step(&mut state); + match &person.agenda { + Agenda::Traveling(_) => {}, + _ => { panic!("Person should be traveling") }, + + } + } } \ No newline at end of file diff --git a/src/state.rs b/src/state.rs index d45e986..d572377 100644 --- a/src/state.rs +++ b/src/state.rs @@ -1,15 +1,17 @@ +use std::collections::HashMap; use std::rc::Rc; use rand::prelude::*; +use crate::entity::Entity; use crate::person::Person; use crate::time::Time; -use crate::world::{World, Terrain, Town, Tavern, Structure}; +use crate::world::{World, Terrain, Town, Tavern}; use crate::events::{FoundTown, WorldGenesis, PersonGenesis, TavernBuilt}; pub struct GameState { pub time: Time, pub world: World, - pub people: Vec>, + pub people: HashMap, pub events: Vec>, pub tavern: Option>, } @@ -41,7 +43,7 @@ impl GameState { GameState { time: Time { time: 0 }, - people: Vec::new(), + people: HashMap::new(), world: world, events: events, tavern: None, @@ -58,9 +60,12 @@ impl GameState { } pub fn step(&mut self) { - // for p in self.people.iter() { - // p.step(self); - // } + let ids: Vec = self.people.keys().map(|id| *id).collect(); + for id in ids { + let mut person = self.people.get_mut(&id).unwrap().clone(); + person.step(self); + self.people.insert(id, person); + } self.time.time += 1; } @@ -81,17 +86,20 @@ impl GameState { * Getters */ - pub fn guests(&self) -> Vec> { + pub fn guests(&self) -> Vec { match &self.tavern { Some(tavern) => { let loc = self.world.get_tavern_location(tavern); - self.people.iter().filter(|person| { + self.people.values().filter(|person| { person.location == loc }).map(|p| p.clone()).collect() }, None => Vec::new(), - } - + } + } + + pub fn get_person(&self, id: u32) -> Option<&Person> { + self.people.get(&id) } /** @@ -124,11 +132,11 @@ impl GameState { time: self.time, effect: Box::new(PersonGenesis { town: town.clone(), - person: Rc::new(Person::new( + person: Person::new( self.time.substract_years(rng.gen_range(18..30)), town.clone(), self.world.get_town_location(&town) - )), + ), }) }); } @@ -137,8 +145,10 @@ impl GameState { } } - pub fn add_person(&mut self, person: Rc) { - self.people.push(person); + pub fn add_person(&mut self, person: &mut Person) { + let id = (self.people.len() + 1) as u32; // avoid 0 id + person.set_id(id); + self.people.insert(id, person.clone()); } pub fn build_tavern(&mut self) { @@ -158,7 +168,55 @@ impl GameState { tavern: Rc::new(Tavern::new()), }) }); + break; } } } +} + +#[cfg(test)] +mod tests { + + use super::*; + + #[test] + fn test_step() { + let mut state = GameState::new(World::new(100)); + state.step(); + assert_eq!(state.time.time, 1); + } + + #[test] + fn test_step_n() { + let mut state = GameState::new(World::new(100)); + state.step_n(10); + assert_eq!(state.time.time, 10); + } + + #[test] + fn test_step_year() { + let mut state = GameState::new(World::new(100)); + state.step_year(); + assert_eq!(state.time.time, 360); + } + + #[test] + fn test_found_town() { + let mut state = GameState::new(World::new(1)); + state.world.map[0][0].terrain = Terrain::Flats; + state.time = Time { time: 1e6 as u32 }; + state.found_town(); + assert_ne!(state.people.len(), 0); + } + + #[test] + fn test_build_tavern() { + let mut state = GameState::new(World::new(2)); + state.world.map[0][0].terrain = Terrain::Flats; + state.world.map[0][1].terrain = Terrain::Flats; + state.world.map[1][0].terrain = Terrain::Hills; + state.world.map[1][1].terrain = Terrain::Hills; + state.build_tavern(); + assert_eq!(state.tavern.is_some(), true); + } } \ No newline at end of file