diff --git a/src/es03_game/cell.rs b/src/es03_game/cell.rs index 619b016..fc60384 100644 --- a/src/es03_game/cell.rs +++ b/src/es03_game/cell.rs @@ -1,4 +1,4 @@ -use super::entities::{Action, Direction, Entity}; +use super::{entities::{Action, Direction, Entity}, floor::Floor}; use dyn_clone::{clone_trait_object, DynClone}; use rand::Rng; use serde::{Deserialize, Serialize}; @@ -81,7 +81,7 @@ pub trait Effect: DynClone { /// elaborato come una trappola di nemici.\ /// Tramite l'entità si può anche accedere al piano dove si trova per /// poter modificare eventualmente qualcosa. - fn apply_to(&self, entity: &mut Entity); + fn apply_to(&self, entity: &mut Entity, floor: &mut Floor); } clone_trait_object!(Effect); @@ -96,7 +96,7 @@ impl Effect for InstantDamage { fn is_persistent(&self) -> bool { false } - fn apply_to(&self, entity: &mut Entity) { + fn apply_to(&self, entity: &mut Entity, _floor: &mut Floor) { entity.apply_damage(self.0); } } @@ -112,10 +112,8 @@ impl Effect for Confusion { fn is_persistent(&self) -> bool { true } - fn apply_to(&self, entity: &mut Entity) { + fn apply_to(&self, entity: &mut Entity, floor: &mut Floor) { if self.0 > 0 { - let mut floor = entity.get_floor(); - let mut floor = floor.get(); let rng = floor.get_rng(); let coin_flip = rng.gen_range(0..=1); if coin_flip == 1 { @@ -141,7 +139,7 @@ impl Effect for TurnBasedDamage { fn is_persistent(&self) -> bool { false } - fn apply_to(&self, entity: &mut Entity) { + fn apply_to(&self, entity: &mut Entity, _floor: &mut Floor) { if self.time > 0 { entity.apply_damage(self.damage); entity.add_effect(Box::new(Self { diff --git a/src/es03_game/entities.rs b/src/es03_game/entities.rs index c2d388e..4a9fba3 100644 --- a/src/es03_game/entities.rs +++ b/src/es03_game/entities.rs @@ -1,6 +1,6 @@ use super::{ cell::Effect, - floor::{FloorPtr, FloorView}, + floor::{Floor, FloorView}, }; use dyn_clone::{clone_trait_object, DynClone}; use rand::Rng; @@ -88,7 +88,6 @@ pub struct Entity { name: String, effects: VecDeque>, behavior: Box, - floor: FloorPtr, pub buffer: Action, pub position: Position, pub direction: Direction, @@ -107,14 +106,11 @@ impl Entity { health: i32, attack: i32, behavior: Box, - mut floor: FloorPtr, ) -> Self { - let position = floor.get().get_entrance(); Self { name, - floor, behavior, - position, + position: Position(0, 0), attack, health, health_max: health, @@ -158,51 +154,34 @@ impl Entity { }; } - /// Restituisce il piano in cui si trova l'entità in questo momento. - pub fn get_floor(&self) -> FloorPtr { - self.floor.clone() - } - - /// Modifica il piano dell'entità e la mette all'entrata di quello nuovo. - pub fn set_floor(&mut self, floor: FloorPtr) { - self.floor = floor; - self.position = self.floor.get().get_entrance(); - } - - /// Permette all'entità di fare un'azione e successivamente calcola - /// tutti gli effetti che devono essere applicati ad essa.\ + /// Permette all'entità di mostrare il piano in cui si trova e di fare una mossa.\ + /// Il piano viene mostrato tramite il behavior dell'entità e successivamente viene chiesto di fare un'azione.\ + /// Dopodichè vengono calcolati tutti gli effetti che devono essere applicati all'entità.\ /// Nel caso in cui l'entità non sia più in vita questo metodo ritornerà false - /// e non permetterà all'entità di fare un update.\ + /// e non permetterà all'entità di fare update.\ /// Nel caso in cui l'entità non riesca a fare l'update viene ritornato false.\ /// Cio significa che l'entità verrà rimossa dal gioco. - pub fn update(&mut self) -> bool { - if self.is_alive() && matches!(self.compute_action(), Some(_)) { - self.compute_effects(); + pub fn update(&mut self, floor: &mut Floor) -> bool { + self.behavior.update(floor.get_limited_view_floor(self)); + if self.is_alive() && matches!(self.compute_action(floor), Some(_)) { + self.compute_effects(floor); return true; } false } - /// Permette all'entità di mostrare il piano in cui si trova e di fare una mossa.\ - /// Ha la stessa funzionalità di update() ma prima mostra il piano dell'entità.\ - /// Il piano viene mostrato tramite il behavior dell'entità. - pub fn update_display(&mut self, floor: FloorView) -> bool { - self.behavior.update(floor); - self.update() - } - /// calcola gli effetti e li applica all'entità. - fn compute_effects(&mut self) { + fn compute_effects(&mut self, floor: &mut Floor) { let total = self.effects.len(); // len could change for _ in 0..total { if let Some(effect) = self.effects.pop_front() { - effect.apply_to(self); + effect.apply_to(self, floor); } } } /// prende una decisione e applica l'azione da fare /// L'azione compiuta viene restituita, altrimenti None - fn compute_action(&mut self) -> Option { + fn compute_action(&mut self, floor: &mut Floor) -> Option { let action = self.behavior.get_next_action()?; let action = match self.buffer { Action::DoNothing => action, @@ -210,7 +189,7 @@ impl Entity { }; let result = Some(action.clone()); - action.apply(self); + action.apply(self, floor); result } } @@ -248,7 +227,7 @@ impl Action { /// \ /// Es. Move(Up) sposterà l'entità da una posizione (x,y) -> (x,y+1)\ /// e applicherà qualunque effetto che si trovi sulla cella di destinazione - pub fn apply(self, entity: &mut Entity) { + pub fn apply(self, entity: &mut Entity, floor: &mut Floor) { match self { Action::DoNothing => {} Action::Move(direction) => { @@ -256,8 +235,6 @@ impl Action { entity.direction = direction; entity.position = pos; - let mut floor = entity.floor.clone(); - let mut floor = floor.get(); let cell = floor.get_cell(pos); cell.entity_over(entity); } diff --git a/src/es03_game/floor.rs b/src/es03_game/floor.rs index 1d820d1..50df6ed 100644 --- a/src/es03_game/floor.rs +++ b/src/es03_game/floor.rs @@ -4,36 +4,7 @@ use super::{ }; use rand_pcg::Pcg32; use serde::{Deserialize, Serialize}; -use std::{ - cell::{RefCell, RefMut}, - fmt::Display, - rc::Rc, -}; - -/// Tupla creata per poter implementare qualche metodo sulla struttura Rc>\ -/// In questo modo ho incapsulato i borrow e la creazione di questo oggetto per una -/// migliore lettura del codice (hopefully). -#[derive(Clone, Deserialize, Serialize)] -pub struct FloorPtr(Rc>); -impl FloorPtr { - /// Crea un nuovo puntatore al piano indicato.\ - /// Il piano viene creato a partire dai parametri passati in input, che sono tutte cose - /// necessarie ad esso. - pub fn new(level: usize, rng: Pcg32, entities: Vec, grid: Vec>) -> Self { - Self(Rc::new(RefCell::new(Floor { - level, - rng, - players: vec![], - entities, - grid, - }))) - } - - /// Permette di prendere il valore puntato al piano. - pub fn get(&mut self) -> RefMut { - self.0.borrow_mut() - } -} +use std::{collections::VecDeque, fmt::Display}; /// Indica un piano del dungeon, in essa si possono trovare le celle in cui si /// cammina e le entità che abitano il piano.\ @@ -42,12 +13,36 @@ impl FloorPtr { pub struct Floor { level: usize, grid: Vec>, - players: Vec, - entities: Vec, + players: VecDeque, + entities: VecDeque, rng: Pcg32, } impl Floor { + /// Crea un nuovo piano al livello indicato.\ + /// Il piano viene creato a partire dai parametri passati in input, che sono tutte cose necessarie ad esso. + pub fn new(level: usize, rng: Pcg32, entities: Vec, grid: Vec>) -> Self { + Self { + level, + rng, + players: VecDeque::new(), + entities: VecDeque::from(entities), + grid, + } + } + + /// Aggiunge un giocatore al piano e lo inserisce all'entrata. + pub fn add_player(&mut self, mut player: Entity) { + // todo!() check collision with other entities + player.position = self.get_entrance(); + self.players.push_back(player); + } + + /// Indica se il piano ha almeno un giocatore in vita o meno + pub fn has_players(&self) -> bool { + self.players.iter().any(|player| player.is_alive()) + } + /// Restituisce il livello di profondità del piano pub fn get_level(&self) -> usize { self.level @@ -83,78 +78,66 @@ impl Floor { .expect("Entrance of the floor should be inside the grid!") } + /// Fa l'update di tutti i giocatori e rimuove eventualmente quelli non più in vita, restituendoli dentro un vec + pub fn update_players(&mut self) -> Vec { + let mut remove = vec![]; + for _ in 0..self.players.len() { + let mut player = self.players.pop_front().unwrap(); + if player.update(self) { + self.players.push_back(player); + } else { + remove.push(player); + } + } + remove + } + /// Fa l'update di tutte le entità e rimuove eventualmente quelle non più in vita pub fn update_entities(&mut self) { - let to_remove: Vec = self - .entities - .iter_mut() - .map(|entity| entity.update()) - .collect(); - let mut to_remove = to_remove.iter(); - self.entities.retain(|_| *to_remove.next().unwrap()); + for _ in 0..self.entities.len() { + let mut entity = self.entities.pop_front().unwrap(); + if entity.update(self) { + self.entities.push_back(entity); + } + } + } + + /// Crea una view del piano.\ + pub fn get_limited_view_floor<'a>(&'a self, entity: &'a Entity) -> FloorView<'a> { + FloorView::new(self, entity) } } /// Struttura di mezzo tra un piano e il gioco vero e proprio.\ /// Utilizzata per la comunicazione con le entità per poter aggiornare quello che vedono.\ /// Infatti internamente ha solo alcuni pezzi del gioco per non far mostrare tutto.\ -pub struct FloorView { - pub level: usize, - pub entity: Entity, - pub players: Vec, - pub entities: Vec, - pub grid: Vec>, +pub struct FloorView<'a> { + pub entity: &'a Entity, + pub floor: &'a Floor, } -impl FloorView { - /// Crea una vista del gioco corrente secondo la visione dell entità passata in intput.\ +impl<'a> FloorView<'a> { + /// Crea una vista del gioco corrente secondo la visione dell'entità passata in intput.\ /// Il SimpleFloor risultante avrà il piano, entità, livello e giocatori che si trovano /// in questo momento sul piano dell'entità passata in input. - pub fn new(entity: &Entity) -> Self { - let mut floor = entity.get_floor(); - let floor = floor.get(); - - let level = floor.level; - let grid = floor.grid.clone(); - let entities: Vec = floor - .entities - .iter() - .filter_map(|e| (e.position != entity.position).then_some(e.clone())) - .collect(); - let players: Vec = floor - .players - .iter() - .filter_map(|p| (p.position != entity.position).then_some(p.clone())) - .collect(); - + pub fn new(floor: &'a Floor, entity: &'a Entity) -> Self { Self { - level, - entity: entity.clone(), - players, - entities, - grid, + entity: &entity, + floor: &floor, } } /// Rappresentazione del piano come matrice di char pub fn as_char_grid(&self) -> Vec> { - let size = self.grid.len(); + let grid = &self.floor.grid; + let size = grid.len(); let mut grid: Vec> = (0..size) .map(|y| { - let row = (0..size).map(|x| Some(&self.grid[x][y])); + let row = (0..size).map(|x| Some(&grid[x][y])); let mut row: Vec<_> = row .clone() .zip(row.skip(1).chain(std::iter::once(None))) - .flat_map(|(a, b)| { - let a = a.unwrap(); - if let Some(b) = b { - let one_is_wall = matches!(b, Cell::Wall) || matches!(a, Cell::Wall); - let c = if one_is_wall { Cell::Wall } else { Cell::Empty }; - vec![a.as_char(), c.as_char()] - } else { - vec![a.as_char()] - } - }) + .flat_map(Self::increase_x_dimension) .collect(); row.push('\n'); row @@ -165,18 +148,27 @@ impl FloorView { grid[pos.1][pos.0 * 2] = self.entity.direction.as_char(); grid } + + fn increase_x_dimension(tuple: (Option<&Cell>, Option<&Cell>)) -> Vec { + let (a, b) = tuple; + let a = a.unwrap(); + if let Some(b) = b { + let one_is_wall = matches!(b, Cell::Wall) || matches!(a, Cell::Wall); + let c = if one_is_wall { Cell::Wall } else { Cell::Empty }; + vec![a.as_char(), c.as_char()] + } else { + vec![a.as_char()] + } + } } -impl Display for FloorView { +impl<'a> Display for FloorView<'a> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let grid: String = self .as_char_grid() .iter() .rev() - .map(|row| { - let a: String = row.iter().collect(); - a - }) + .map(|row| row.iter().collect::()) .collect(); write!(f, "{}\n{}", grid, self.entity) diff --git a/src/es03_game/game.rs b/src/es03_game/game.rs index 0fd00f8..db3929d 100644 --- a/src/es03_game/game.rs +++ b/src/es03_game/game.rs @@ -1,8 +1,7 @@ use super::{ - cell::Cell, config::Config, entities::{Behavior, Entity}, - floor::{FloorPtr, FloorView}, + floor::Floor, generator::Generator, }; use rand::{RngCore, SeedableRng}; @@ -14,10 +13,9 @@ use serde::{Deserialize, Serialize}; /// e dei giocatori che esplorano. #[derive(Clone, Deserialize, Serialize)] pub struct Dungeon { - floors: Vec, - rng: Pcg32, + floors: Vec, config: Config, - players: Vec, + rng: Pcg32, } impl Dungeon { @@ -31,7 +29,6 @@ impl Dungeon { let mut game = Self { rng: Pcg32::seed_from_u64(config.game_seed), floors: vec![], - players: vec![], config, }; game.build_next_floor(); @@ -42,47 +39,52 @@ impl Dungeon { /// ad esso tramite la configurazione indicata nel costruttore.\ /// Il giocatore appena inserito si troverà al piano 0. pub fn add_player(&mut self, name: String, decider: Box) { - let floor = self.floors[0].clone(); let stats = &self.config.player_stats; - let entity = Entity::new(name, stats.health, stats.attack, decider, floor); - self.players.push(entity); + let player = Entity::new(name, stats.health, stats.attack, decider); + self.floors[0].add_player(player); } /// Indica se nel dungeon ci sono dei giocatori.\ /// Metodo utile, dato che nel caso in cui non ci siano, il dungen non verrà modificato /// siccome per calcolare il turno successivo ho bisogno di giocatori. - pub fn has_players(&self) -> bool { - !self.get_players().is_empty() - } - - /// Restiutuisce la lista dei giocatori che in questo momento stanno giocando. - pub fn get_players(&self) -> &Vec { - &self.players + pub fn has_players(&mut self) -> bool { + self.floors.iter().any(|floor| floor.has_players()) } /// Restituisce il piano indicato dal livello di profondità.\ /// Nel caso il livello non esista, restituisce il piano con profondità maggiore. - pub fn get_floor(&self, level: usize) -> FloorPtr { + pub fn get_floor(&self, level: usize) -> &Floor { let floors = self.floors.len() - 1; let index = level.min(floors); - self.floors[index].clone() + &self.floors[index] } /// Funzione principale del dungeon.\ /// In essa viene fatto fare l'update ai giocatori e ad ogni piano. - /// In generale l'algoritmo è il seguente:\ + /// In generale l'algoritmo è il seguente per ogni piano in cui si trova un giocatore:\ /// - I giocatori fanno le loro mosse.\ /// - Se un giocatore non è più in vita o non può indicare l'azione da fare, viene rimosso - /// - Update di tutti i piani in cui c'è almeno un giocatore + /// - Update di tutte le entità del piano /// - Modifica di piano di eventuali giocatori pub fn compute_turn(&mut self) { - if !self.has_players() { - return; - } + let moved: Option> = self.floors.iter_mut().fold(None, |moved, floor| { + let removed = floor.has_players().then(|| { + let removed = floor.update_players(); + floor.update_entities(); + removed + }); + if let Some(mut moved) = moved { + moved.drain(..).for_each(|player| floor.add_player(player)); + } + removed + }); - let mut update = UpdateDungeon::compute(self); - update.update_floors(); - update.remove_eventual_players(); + if let Some(mut moved) = moved { + self.build_next_floor(); + let len = self.floors.len(); + let floor = &mut self.floors[len - 1]; + moved.drain(..).for_each(|player| floor.add_player(player)); + } } /// permette di costruire il piano successivo @@ -93,94 +95,4 @@ impl Dungeon { let floor = generator.build_floor(); self.floors.push(floor); } - /// restituisce il piano indicato o ne crea uno nuovo se il livello è troppo profondo - fn get_floor_or_build(&mut self, level: usize) -> FloorPtr { - let mut level = level; - if level > self.floors.len() { - level = self.floors.len(); - } - if level == self.floors.len() { - self.build_next_floor() - } - - self.get_floor(level) - } -} - -/// Serve al dungeon per fare tutti i vari update.\ -/// È stata creata una struttura e una sua implementazione apposta dato che per gli update la logica -/// è sia complessa che contorta. In questo modo si riesce a capire meglio che cosa viene fatto -/// utilizzando delle funzioni apposta. -struct UpdateDungeon<'a> { - dungeon: &'a mut Dungeon, - players: Vec, - update_floors: Vec, - change_floors: Vec, -} - -impl<'a> UpdateDungeon<'a> { - /// Crea un update del dungeon.\ - /// Con questo metodo si inizializza la struttura e per farlo viene chiamata la funzione - /// update_display per ogni player che il dungeon ha attivo.\ - /// Dopo questo metodo, la struttura che ne risulta ha salvato alcuni parametri che possono - /// diventare obsoleti nel caso in cui i metodo vangano chiamati dopo troppo tempo. - fn compute(dungeon: &'a mut Dungeon) -> Self { - let mut update_floors = vec![false; dungeon.floors.len()]; - let mut change_floors = vec![0; dungeon.players.len()]; - - let players: Vec = (0..dungeon.players.len()) - .into_iter() - .map(|i| { - let player = &mut dungeon.players[i]; - let floor = FloorView::new(&player); - let value = player.update_display(floor); - - let mut floor = player.get_floor(); - let mut floor = floor.get(); - update_floors[floor.get_level()] = true; - if let Cell::Exit = floor.get_cell(player.position) { - change_floors[i] = floor.get_level() + 1; - } - - value - }) - .collect(); - Self { - dungeon, - players, - update_floors, - change_floors, - } - } - - /// Permette di fare l'update dei tutti i piani che hanno giocatori attivi.\ - /// I giocatori attivi vengono calcolati appena viene creata l'istanza, quindi - /// questo metodo può diventare obsoleto nel caso in cui venga chiamato dopo troppo tempo - /// dall'inizializzazione della struttura. - fn update_floors(&mut self) -> &mut Self { - self.update_floors - .iter() - .enumerate() - .filter_map(|(i, b)| (*b).then(|| i)) - .for_each(|i| self.dungeon.floors[i].get().update_entities()); - self.change_floors - .iter() - .enumerate() - .filter(|(_, f)| **f != 0) - .for_each(|(player, floor)| { - let floor = self.dungeon.get_floor_or_build(*floor); - let player = &mut self.dungeon.players[player]; - player.set_floor(floor); - }); - self - } - - /// Permette di rimuovare eventuali giocatori che non servono più.\ - /// Questo metodo prende l'ownership della struttura dato che deve essere chiamato per ultimo - /// siccome può modificare la lunghezza del vettore di players, invalidando quindi - /// tutti i parametri creati precedentemente. - fn remove_eventual_players(self) { - let mut players = self.players.iter(); - self.dungeon.players.retain(|_| *players.next().unwrap()); - } } diff --git a/src/es03_game/generator.rs b/src/es03_game/generator.rs index b707182..31d494e 100644 --- a/src/es03_game/generator.rs +++ b/src/es03_game/generator.rs @@ -1,7 +1,7 @@ use super::{ cell::Cell, config::{Config, ConfigEffect, ConfigEntity}, - floor::{Floor, FloorPtr}, + floor::Floor, }; use rand::{Rng, SeedableRng}; use rand_pcg::Pcg32; @@ -46,7 +46,7 @@ impl Generator { /// Questo metodo è il più semplice per la generazione del piano.\ /// Crea una enorme stanza con dei muri attorno e piazza tutti gli effetti. - pub fn build_floor(mut self) -> FloorPtr { + pub fn build_floor(mut self) -> Floor { for x in 1..(self.size - 1) { for y in 1..(self.size - 1) { self.grid[x][y] = Cell::Empty; @@ -55,7 +55,7 @@ impl Generator { self.rand_place_cell(Cell::Entance); self.rand_place_effects(); - FloorPtr::new(self.level, self.rng, vec![], self.grid) + Floor::new(self.level, self.rng, vec![], self.grid) } /// TODO