From b4d4c4777721ddef0c470a0bb4c41a636c9be9bb Mon Sep 17 00:00:00 2001 From: Niko Abeler Date: Wed, 4 Jan 2023 14:37:25 +0100 Subject: [PATCH] more refactoring --- src/app.rs | 31 +++++++++------ src/entity.rs | 15 +++++-- src/events/found_town.rs | 13 +++--- src/events/person_genesis.rs | 12 ++++-- src/events/tavern_built.rs | 13 +++--- src/events/world_genesis.rs | 8 +++- src/game.rs | 56 ++++++++++++++++++++------ src/person.rs | 76 +++++++++++++++++------------------- src/state.rs | 55 +++++++++++++++----------- src/world.rs | 14 +++---- 10 files changed, 180 insertions(+), 113 deletions(-) diff --git a/src/app.rs b/src/app.rs index 1e9a32e..35cd3b3 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1,7 +1,8 @@ +use std::any::Any; use crossterm::event::{read, Event, KeyCode}; use tui::{backend::Backend, Frame, layout::Layout}; -use crate::{game::Game, person::Person, entity::Entity}; +use crate::{game::Game, person::Creature, entity::{Entity, EntityId}}; /** * |........................| @@ -41,9 +42,16 @@ impl<'a> App<'a> { match &self.status { AppStatus::Initial => { // determine guests - self.guest_list = self.game.state.guests().iter().map(|guest| { - tui::widgets::ListItem::new(guest.name.clone()) - }).collect(); + self.guest_list = vec![]; + // for creature in self.game.state.guests() { + // let creature = self.to_person(&creature); + // let guest = creature.downcast::().unwrap(); + // self.guest_list.push(tui::widgets::ListItem::new(guest.name.clone())); + // } + // self.guest_list = self.game.state.guests().iter().map(|creature: &Box| { + // let guest = creature.downcast::().unwrap(); + // tui::widgets::ListItem::new(guest.name.clone()) + // }).collect(); self.guest_list_state = tui::widgets::ListState::default(); self.guest_list_state.select(Some(0)); self.status = AppStatus::GuestSelection; @@ -67,10 +75,10 @@ impl<'a> App<'a> { }, KeyCode::Enter => { let selected = self.guest_list_state.selected().unwrap(); - let guest = &self.game.state.guests()[selected]; + let guest_id = &self.game.state.guests()[selected]; self.conversation = Vec::new(); - self.greet_guest(guest); - self.status = AppStatus::TalkToGuest(guest.id()); + self.greet_guest(Some(*guest_id)); + self.status = AppStatus::TalkToGuest(Some(*guest_id)); }, KeyCode::Char('.') => { self.game.step(); @@ -166,7 +174,7 @@ impl<'a> App<'a> { f.render_widget(controls, chunks[1]); } fn draw_talk_to_guest(&self, f: &mut Frame, guest: Option) { - let guest = self.game.state.get_person(guest.unwrap()).unwrap(); + let guest = self.game.state.get_creature(guest.unwrap()).unwrap(); let key_style = tui::style::Style::default() .add_modifier(tui::style::Modifier::BOLD) @@ -189,7 +197,8 @@ impl<'a> App<'a> { /** * Conversation */ - fn greet_guest(&mut self, guest: &Person) { + fn greet_guest(&mut self, guest_id: Option) { + let guest = self.game.state.get_creature(guest_id.unwrap()).unwrap(); self.conversation.push( tui::text::Spans::from(vec![ tui::text::Span::styled("Greetings traveller?\n", tui::style::Style::default().fg(tui::style::Color::Yellow)), @@ -204,8 +213,8 @@ impl<'a> App<'a> { ); } - fn ask_business(&mut self, guest: Option) { - let guest = self.game.state.get_person(guest.unwrap()).unwrap(); + fn ask_business(&mut self, guest: Option) { + let guest = self.game.state.get_creature(guest.unwrap()).unwrap(); self.conversation.push( tui::text::Spans::from(vec![ tui::text::Span::styled("What's your business?\n", tui::style::Style::default().fg(tui::style::Color::Yellow)) diff --git a/src/entity.rs b/src/entity.rs index 5966059..1f3e987 100644 --- a/src/entity.rs +++ b/src/entity.rs @@ -1,5 +1,14 @@ -pub trait Entity { - fn id(&self) -> Option; - fn set_id(&mut self, id: u32); + +pub type EntityId = u32; + +#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)] +pub struct Location { + pub x: i32, + pub y: i32, } +#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)] +pub struct Entity { + pub id: EntityId, + pub loc: Location +} \ No newline at end of file diff --git a/src/events/found_town.rs b/src/events/found_town.rs index 2c466bb..8bf4416 100644 --- a/src/events/found_town.rs +++ b/src/events/found_town.rs @@ -1,19 +1,22 @@ use std::rc::Rc; -use crate::{state::Effect, world::{Town, Structure}, state::GameState}; +use crate::{state::Action, world::{Town, Structure}, state::GameState, entity::Location}; pub struct FoundTown{ - pub x: usize, - pub y: usize, + pub loc: Location, pub town: Rc, } -impl Effect for FoundTown { +impl Action for FoundTown { fn apply(&self, state: &mut GameState) { - state.world.add_structure(self.x, self.y, Structure::Town(self.town.clone())); + state.world.add_structure(self.loc, Structure::Town(self.town.clone())); } fn description(&self) -> String { format!("{} was founded", self.town.name) } + + fn notable(&self) -> bool { + true + } } \ No newline at end of file diff --git a/src/events/person_genesis.rs b/src/events/person_genesis.rs index 1b82e19..d58fc4e 100644 --- a/src/events/person_genesis.rs +++ b/src/events/person_genesis.rs @@ -1,18 +1,22 @@ use std::rc::Rc; -use crate::{state::{GameState, Effect}, world::Town, person::Person}; +use crate::{state::{GameState, Action}, world::Town, person::Creature}; pub struct PersonGenesis { pub town: Rc, - pub person: Person, + pub person: Creature, } -impl Effect for PersonGenesis { +impl Action for PersonGenesis { fn apply(&self, state: &mut GameState) { - state.add_person(&mut self.person.clone()); + state.add_person(self.person.clone()); } fn description(&self) -> String { format!("{} was sent by the Gods to {}", self.person.name, self.town.name) } + + fn notable(&self) -> bool { + true + } } \ No newline at end of file diff --git a/src/events/tavern_built.rs b/src/events/tavern_built.rs index e032c0a..9084c90 100644 --- a/src/events/tavern_built.rs +++ b/src/events/tavern_built.rs @@ -1,20 +1,23 @@ use std::rc::Rc; -use crate::{state::{GameState, Effect}, world::{Structure, Tavern}}; +use crate::{state::{GameState, Action}, world::{Structure, Tavern}, entity::Location}; pub struct TavernBuilt { - pub x: usize, - pub y: usize, + pub loc: Location, pub tavern: Rc, } -impl Effect for TavernBuilt { +impl Action for TavernBuilt { fn apply(&self, state: &mut GameState) { - state.world.add_structure(self.x, self.y, Structure::Tavern(self.tavern.clone())); + state.world.add_structure(self.loc, Structure::Tavern(self.tavern.clone())); state.set_tavern(self.tavern.clone()); } fn description(&self) -> String { format!("{} was built", self.tavern.name) } + + fn notable(&self) -> bool { + true + } } \ No newline at end of file diff --git a/src/events/world_genesis.rs b/src/events/world_genesis.rs index a4fa468..ebda5c6 100644 --- a/src/events/world_genesis.rs +++ b/src/events/world_genesis.rs @@ -1,8 +1,8 @@ -use crate::state::{GameState, Effect}; +use crate::state::{GameState, Action}; pub struct WorldGenesis; -impl Effect for WorldGenesis { +impl Action for WorldGenesis { fn apply(&self, _state: &mut GameState) { return; } @@ -10,4 +10,8 @@ impl Effect for WorldGenesis { fn description(&self) -> String { "World was created".to_string() } + + fn notable(&self) -> bool { + true + } } \ No newline at end of file diff --git a/src/game.rs b/src/game.rs index b906c24..46a2d2d 100644 --- a/src/game.rs +++ b/src/game.rs @@ -1,4 +1,4 @@ -use crate::state::GameState; +use crate::state::{GameState, Event}; /** * This is the main game struct. @@ -18,19 +18,24 @@ impl Game { pub fn step(&mut self) { // get list of all people ids - let ids: Vec = self.state.people.keys().map(|id| *id).collect(); + let ids: Vec = self.state.creatures.keys().map(|id| *id).collect(); // step each person for id in ids { - let person = self.state.people.get_mut(&id); - match person { - Some(p) => { - let mut p = p.clone(); - p.step(&mut self.state); - self.state.people.insert(id, p); - }, - // person is dead - None => (), + let person = self.state.creatures.get(&id); + if let Some(p) = person { + let mut p = p.clone(); + let actions = p.step(&self.state.world); + for action in actions { + action.apply(&mut self.state); + if action.notable() { + self.state.events.push(Box::new(Event{ + time: self.state.time, + effect: action + })) + } + } + self.state.creatures.insert(p.entity.id, p); } } @@ -43,7 +48,9 @@ impl Game { #[cfg(test)] mod tests { - use crate::world::World; + use std::rc::Rc; + + use crate::{world::{World, Terrain, Town}, person::{Creature, Agenda}, time::Time, entity::Location}; use super::*; @@ -55,4 +62,29 @@ mod tests { assert_eq!(game.state.time.time, 1); } + #[test] + fn test_step_creature() { + 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; + + let mut creature = Creature::new( + Time { time: 0 }, + Rc::new(Town::new()), + Location { x: 0, y: 0 } + ); + creature.set_agenda(Agenda::Traveling(Location { x: 2, y: 2 })); + state.add_person(creature); + + let mut game = Game::new(state); + game.step(); + + assert_eq!(game.state.creatures.len(), 1); + assert_eq!(game.state.creatures[&1].entity.loc, Location { x: 1, y: 1 }); + + } + + } \ No newline at end of file diff --git a/src/person.rs b/src/person.rs index 967a506..86f4ad9 100644 --- a/src/person.rs +++ b/src/person.rs @@ -1,7 +1,7 @@ use std::{rc::Rc, fmt::Display}; use rand::prelude::*; -use crate::{time::Time, world::Town, generators::PersonNameGenerator, state::GameState, entity::Entity}; +use crate::{time::Time, world::{Town, World}, generators::PersonNameGenerator, state::{GameState, Action}, entity::{Entity, Location}}; #[derive(Clone, Copy)] pub enum Profession { @@ -13,66 +13,70 @@ pub enum Profession { #[derive(Clone, Copy)] pub enum Agenda { Idle(u32), - Traveling([usize; 2]), + Traveling(Location), } #[derive(Clone)] -pub struct Person { - pub id: Option, +pub struct Creature { + pub entity: Entity, pub name: String, pub birth_date: Time, pub birth_location: Rc, pub profession: Profession, - pub location: [usize; 2], pub agenda: Agenda, } -impl Person { - pub fn new(birth_date: Time, birth_location: Rc, location: [usize; 2]) -> Person { - Person { - id: None, +impl Creature { + pub fn new(birth_date: Time, birth_location: Rc, location: Location) -> Creature { + Creature { + entity: Entity { id: 0, loc: location }, name: PersonNameGenerator::name(), birth_date: birth_date, birth_location: birth_location, profession: Profession::Peasant, - location: location, agenda: Agenda::Idle(0), } } - pub fn step(&mut self, state: &mut GameState) { + pub fn step(&mut self, world: &World) -> Vec> { match &self.agenda { Agenda::Idle(days) => { // do nothing if *days <= 0 { // pick random destination let mut rng = rand::thread_rng(); - let dest = state.world.structures.keys().choose(&mut rng); + let dest = world.structures.keys().choose(&mut rng); self.agenda = Agenda::Traveling(*dest.unwrap()); } else { self.agenda = Agenda::Idle(days - 1); } + Vec::new() }, Agenda::Traveling(destination) => { // TDOO: A* pathfinding with terrain costs // move towards destination - if self.location[0] < destination[0] { - self.location[0] += 1; - } else if self.location[0] > destination[0] { - self.location[0] -= 1; + if self.entity.loc.x < destination.x { + self.entity.loc.x += 1; + } else if self.entity.loc.x > destination.x { + self.entity.loc.x -= 1; } - if self.location[1] < destination[1] { - self.location[1] += 1; - } else if self.location[1] > destination[1] { - self.location[1] -= 1; + if self.entity.loc.y < destination.y { + self.entity.loc.y += 1; + } else if self.entity.loc.y > destination.y { + self.entity.loc.y -= 1; } - if self.location == *destination { + if self.entity.loc == *destination { self.agenda = Agenda::Idle(10); } + Vec::new() }, } } + pub fn set_agenda(&mut self, agenda: Agenda) { + self.agenda = agenda; + } + pub fn say_agenda(&self, state: & GameState) -> String { match &self.agenda { Agenda::Idle(days) => format!("I'll stay here for {} days", days), @@ -89,22 +93,12 @@ impl Person { } } -impl Display for Person { +impl Display for Creature { 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 { @@ -114,25 +108,25 @@ mod tests { #[test] fn test_person_creation() { - let person = Person::new(Time { time: 0 }, Rc::new(Town::new()), [0, 0]); + let person = Creature::new(Time { time: 0 }, Rc::new(Town::new()), Location{x: 0, y: 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]); + let mut person = Creature::new(Time { time: 0 }, Rc::new(Town::new()), Location{x: 0, y: 0}); + person.agenda = Agenda::Traveling(Location{x: 10, y: 0}); + person.step(&World::new(32)); + assert_eq!(person.entity.loc, Location{x: 1, y: 0}); } #[test] fn test_start_traveling() { - let mut person = Person::new(Time { time: 0 }, Rc::new(Town::new()), [0, 0]); + let mut person = Creature::new(Time { time: 0 }, Rc::new(Town::new()), Location{x: 0, y: 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); + let mut world = World::new(32); + world.add_structure(Location{x: 10, y: 10}, Structure::Town(Rc::new(Town::new()))); + person.step(&world); match &person.agenda { Agenda::Traveling(_) => {}, _ => { panic!("Person should be traveling") }, diff --git a/src/state.rs b/src/state.rs index 38610f7..f6bfcb0 100644 --- a/src/state.rs +++ b/src/state.rs @@ -3,27 +3,34 @@ use std::collections::HashMap; use std::rc::Rc; use rand::prelude::*; -use crate::entity::Entity; -use crate::person::Person; +use crate::entity::{Location, EntityId}; +use crate::person::Creature; 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: HashMap, + pub creatures: HashMap, pub events: Vec>, pub tavern: Option>, } pub struct Event { pub time: Time, - pub effect: Box, + pub effect: Box, } -pub trait Effect { +pub trait Action { + // apply the effect to the game state fn apply(&self, state: &mut GameState); + // description of the event that will be displayed in the event log + // TODO: thils needs a more complex return type. + // A notable event needs to be related to entities fn description(&self) -> String; + // if true, the event will be added to the event log + // otherwise, it will only be used for internal purposes + fn notable(&self) -> bool; } impl Event { @@ -43,7 +50,7 @@ impl GameState { GameState { time: Time { time: 0 }, - people: HashMap::new(), + creatures: HashMap::new(), world: world, events: events, tavern: None, @@ -64,20 +71,21 @@ 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.values().filter(|person| { - person.location == loc - }).map(|p| p.clone()).collect() + self.creatures.values().filter(|c| { + c.entity.loc == loc + }).map(|p| p.entity.id).collect::>() + // Vec::new() }, None => Vec::new(), } } - pub fn get_person(&self, id: u32) -> Option<&Person> { - self.people.get(&id) + pub fn get_creature(&self, id: u32) -> Option<&Creature> { + self.creatures.get(&id) } @@ -98,8 +106,7 @@ impl GameState { self.add_event(Event { time: self.time, effect: Box::new(FoundTown { - x: x, - y: y, + loc: Location{ x: x as i32, y: y as i32 }, town: town.clone(), }) }); @@ -111,7 +118,7 @@ impl GameState { time: self.time, effect: Box::new(PersonGenesis { town: town.clone(), - person: Person::new( + person: Creature::new( self.time.substract_years(rng.gen_range(18..30)), town.clone(), self.world.get_town_location(&town) @@ -124,10 +131,10 @@ impl GameState { } } - 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 add_person(&mut self, mut creature: Creature) { + let id = (self.creatures.len() + 1) as u32; // avoid 0 id + creature.entity.id = id; + self.creatures.insert(id, creature); } pub fn build_tavern(&mut self) { @@ -142,8 +149,7 @@ impl GameState { self.add_event(Event { time: self.time, effect: Box::new(TavernBuilt { - x: x, - y: y, + loc: Location{ x: x as i32, y: y as i32 }, tavern: Rc::new(Tavern::new()), }) }); @@ -156,6 +162,8 @@ impl GameState { #[cfg(test)] mod tests { + use crate::person::Agenda; + use super::*; #[test] @@ -164,7 +172,7 @@ mod tests { state.world.map[0][0].terrain = Terrain::Flats; state.time = Time { time: 1e6 as i32 }; state.found_town(); - assert_ne!(state.people.len(), 0); + assert_ne!(state.creatures.len(), 0); } #[test] @@ -177,4 +185,5 @@ mod tests { state.build_tavern(); assert_eq!(state.tavern.is_some(), true); } + } \ No newline at end of file diff --git a/src/world.rs b/src/world.rs index 7a41e73..6425449 100644 --- a/src/world.rs +++ b/src/world.rs @@ -1,7 +1,7 @@ use std::{rc::Rc, collections::HashMap, fmt}; use rand::prelude::*; -use crate::generators::TownNameGenerator; +use crate::{generators::TownNameGenerator, entity::Location}; #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum Terrain { @@ -36,7 +36,7 @@ pub struct WorldCell { pub struct World { pub map: Vec>, pub size: usize, - pub structures: HashMap<[usize; 2], Structure> + pub structures: HashMap } impl WorldCell { @@ -64,12 +64,12 @@ impl World { } } - pub fn add_structure(&mut self, x: usize, y: usize, structure: Structure) { - self.structures.insert([x, y], structure); + pub fn add_structure(&mut self, location: Location, structure: Structure) { + self.structures.insert(location, structure); } - pub fn get_town_location(&self, town: &Rc) -> [usize; 2] { + pub fn get_town_location(&self, town: &Rc) -> Location { for (location, structure) in self.structures.iter() { match structure { Structure::Town(t) => { @@ -83,7 +83,7 @@ impl World { panic!("Town not found"); } - pub fn get_tavern_location(&self, tavern: &Rc) -> [usize; 2] { + pub fn get_tavern_location(&self, tavern: &Rc) -> Location { for (location, structure) in self.structures.iter() { match structure { Structure::Tavern(t) => { @@ -98,7 +98,7 @@ impl World { } - pub fn get_structure_at(&self, pos: [usize; 2]) -> Option { + pub fn get_structure_at(&self, pos: Location) -> Option { match self.structures.get(&pos) { Some(s) => Some(s.clone()), None => None,