people traveling

This commit is contained in:
Niko Abeler 2022-12-31 15:22:44 +01:00
parent 6318767e32
commit aa32eaec30
6 changed files with 154 additions and 37 deletions

View File

@ -1,9 +1,7 @@
use std::{rc::Rc};
use crossterm::event::{read, Event, KeyCode}; 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 { enum AppStatus {
Initial, Initial,
GuestSelection, GuestSelection,
TalkToGuest(Rc<Person>), TalkToGuest(Option<u32>),
} }
pub struct App<'a> { pub struct App<'a> {
@ -68,7 +66,7 @@ impl<'a> App<'a> {
KeyCode::Enter => { KeyCode::Enter => {
let selected = self.guest_state.selected().unwrap(); let selected = self.guest_state.selected().unwrap();
let guest = &self.state.guests()[selected]; let guest = &self.state.guests()[selected];
self.status = AppStatus::TalkToGuest(guest.clone()); self.status = AppStatus::TalkToGuest(guest.id());
}, },
KeyCode::Char('.') => { KeyCode::Char('.') => {
self.state.step(); self.state.step();
@ -109,8 +107,8 @@ impl<'a> App<'a> {
AppStatus::GuestSelection => { AppStatus::GuestSelection => {
self.draw_guest_selection(f); self.draw_guest_selection(f);
}, },
AppStatus::TalkToGuest(guest) => { AppStatus::TalkToGuest(guest_id) => {
self.draw_talk_to_guest(f, guest.clone()); self.draw_talk_to_guest(f, *guest_id);
} }
} }
@ -161,7 +159,8 @@ impl<'a> App<'a> {
f.render_widget(controls, chunks[1]); f.render_widget(controls, chunks[1]);
} }
fn draw_talk_to_guest<B: Backend>(&self, f: &mut Frame<B>, guest: Rc<Person>) { fn draw_talk_to_guest<B: Backend>(&self, f: &mut Frame<B>, guest: Option<u32>) {
let guest = self.state.get_person(guest.unwrap()).unwrap();
let main_window = tui::widgets::Paragraph::new(guest.name.clone()) let main_window = tui::widgets::Paragraph::new(guest.name.clone())
.block(tui::widgets::Block::default().title("Guests").borders(tui::widgets::Borders::ALL)) .block(tui::widgets::Block::default().title("Guests").borders(tui::widgets::Borders::ALL))
.style(tui::style::Style::default().fg(tui::style::Color::White)); .style(tui::style::Style::default().fg(tui::style::Color::White));

5
src/entity.rs Normal file
View File

@ -0,0 +1,5 @@
pub trait Entity {
fn id(&self) -> Option<u32>;
fn set_id(&mut self, id: u32);
}

View File

@ -4,12 +4,12 @@ use crate::{state::{GameState, Effect}, world::Town, person::Person};
pub struct PersonGenesis { pub struct PersonGenesis {
pub town: Rc<Town>, pub town: Rc<Town>,
pub person: Rc<Person>, pub person: Person,
} }
impl Effect for PersonGenesis { impl Effect for PersonGenesis {
fn apply(&self, state: &mut GameState) { fn apply(&self, state: &mut GameState) {
state.add_person(self.person.clone()); state.add_person(&mut self.person.clone());
} }
fn description(&self) -> String { fn description(&self) -> String {

View File

@ -5,6 +5,7 @@ mod time;
mod generators; mod generators;
mod person; mod person;
mod app; mod app;
mod entity;
use app::App; use app::App;
use noise::{Perlin, ScalePoint, Add, NoiseFn, ScaleBias}; use noise::{Perlin, ScalePoint, Add, NoiseFn, ScaleBias};
@ -132,10 +133,10 @@ fn main() -> Result<(), io::Error> {
let mut rng = rand::thread_rng(); let mut rng = rand::thread_rng();
for _ in 0..N_TOWNS { for _ in 0..N_TOWNS {
world_state.step_n(rng.gen_range(0..360*3)); world_state.step_n(rng.gen_range(0..360*3));
world_state.found_town(); world_state.found_town();
} }
world_state.build_tavern();
// setup terminal // setup terminal

View File

@ -1,21 +1,24 @@
use std::{rc::Rc, fmt::Display}; use std::{rc::Rc, fmt::Display};
use rand::seq::SliceRandom;
use rand::prelude::*; 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 { pub enum Profession {
Peasant, Peasant,
Adventurer, Adventurer,
Blacksmith, Blacksmith,
} }
#[derive(Clone, Copy)]
pub enum Agenda { pub enum Agenda {
Idle, Idle(u32),
Traveling([usize; 2]), Traveling([usize; 2]),
} }
#[derive(Clone)]
pub struct Person { pub struct Person {
pub id: Option<u32>,
pub name: String, pub name: String,
pub birth_date: Time, pub birth_date: Time,
pub birth_location: Rc<Town>, pub birth_location: Rc<Town>,
@ -27,23 +30,28 @@ pub struct Person {
impl Person { impl Person {
pub fn new(birth_date: Time, birth_location: Rc<Town>, location: [usize; 2]) -> Person { pub fn new(birth_date: Time, birth_location: Rc<Town>, location: [usize; 2]) -> Person {
Person { Person {
id: None,
name: PersonNameGenerator::name(), name: PersonNameGenerator::name(),
birth_date: birth_date, birth_date: birth_date,
birth_location: birth_location, birth_location: birth_location,
profession: Profession::Peasant, profession: Profession::Peasant,
location: [0, 0], location: location,
agenda: Agenda::Idle, agenda: Agenda::Idle(0),
} }
} }
pub fn step(&mut self, state: &mut GameState) { pub fn step(&mut self, state: &mut GameState) {
match &self.agenda { match &self.agenda {
Agenda::Idle => { Agenda::Idle(days) => {
// do nothing // do nothing
if *days <= 0 {
// pick random destination // pick random destination
let mut rng = rand::thread_rng(); let mut rng = rand::thread_rng();
let dest = state.world.structures.keys().choose(&mut rng); let dest = state.world.structures.keys().choose(&mut rng);
self.agenda = Agenda::Traveling(*dest.unwrap()); self.agenda = Agenda::Traveling(*dest.unwrap());
} else {
self.agenda = Agenda::Idle(days - 1);
}
}, },
Agenda::Traveling(destination) => { Agenda::Traveling(destination) => {
// TDOO: A* pathfinding with terrain costs // TDOO: A* pathfinding with terrain costs
@ -59,7 +67,7 @@ impl Person {
self.location[1] -= 1; self.location[1] -= 1;
} }
if self.location == *destination { if self.location == *destination {
self.agenda = Agenda::Idle; self.agenda = Agenda::Idle(10);
} }
}, },
} }
@ -71,3 +79,49 @@ impl Display for Person {
write!(f, "{}", self.name) write!(f, "{}", self.name)
} }
} }
impl Entity for Person {
fn id(&self) -> Option<u32> {
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") },
}
}
}

View File

@ -1,15 +1,17 @@
use std::collections::HashMap;
use std::rc::Rc; use std::rc::Rc;
use rand::prelude::*; use rand::prelude::*;
use crate::entity::Entity;
use crate::person::Person; use crate::person::Person;
use crate::time::Time; 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}; use crate::events::{FoundTown, WorldGenesis, PersonGenesis, TavernBuilt};
pub struct GameState { pub struct GameState {
pub time: Time, pub time: Time,
pub world: World, pub world: World,
pub people: Vec<Rc<Person>>, pub people: HashMap<u32, Person>,
pub events: Vec<Box<Event>>, pub events: Vec<Box<Event>>,
pub tavern: Option<Rc<Tavern>>, pub tavern: Option<Rc<Tavern>>,
} }
@ -41,7 +43,7 @@ impl GameState {
GameState { GameState {
time: Time { time: 0 }, time: Time { time: 0 },
people: Vec::new(), people: HashMap::new(),
world: world, world: world,
events: events, events: events,
tavern: None, tavern: None,
@ -58,9 +60,12 @@ impl GameState {
} }
pub fn step(&mut self) { pub fn step(&mut self) {
// for p in self.people.iter() { let ids: Vec<u32> = self.people.keys().map(|id| *id).collect();
// p.step(self); 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; self.time.time += 1;
} }
@ -81,17 +86,20 @@ impl GameState {
* Getters * Getters
*/ */
pub fn guests(&self) -> Vec<Rc<Person>> { pub fn guests(&self) -> Vec<Person> {
match &self.tavern { match &self.tavern {
Some(tavern) => { Some(tavern) => {
let loc = self.world.get_tavern_location(tavern); let loc = self.world.get_tavern_location(tavern);
self.people.iter().filter(|person| { self.people.values().filter(|person| {
person.location == loc person.location == loc
}).map(|p| p.clone()).collect() }).map(|p| p.clone()).collect()
}, },
None => Vec::new(), 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, time: self.time,
effect: Box::new(PersonGenesis { effect: Box::new(PersonGenesis {
town: town.clone(), town: town.clone(),
person: Rc::new(Person::new( person: Person::new(
self.time.substract_years(rng.gen_range(18..30)), self.time.substract_years(rng.gen_range(18..30)),
town.clone(), town.clone(),
self.world.get_town_location(&town) self.world.get_town_location(&town)
)), ),
}) })
}); });
} }
@ -137,8 +145,10 @@ impl GameState {
} }
} }
pub fn add_person(&mut self, person: Rc<Person>) { pub fn add_person(&mut self, person: &mut Person) {
self.people.push(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) { pub fn build_tavern(&mut self) {
@ -158,7 +168,55 @@ impl GameState {
tavern: Rc::new(Tavern::new()), 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);
}
}