Files
upo-rust/src/es03_game/game.rs
Berack96 d07f22713d Save/Load
- added save/load functions to json
2024-05-14 12:43:05 +02:00

122 lines
4.4 KiB
Rust

use super::{
config::Config,
entities::{Behavior, Entity},
floor::Floor,
generator::Generator,
};
use rand::{RngCore, SeedableRng};
use rand_pcg::Pcg32;
use serde::{Deserialize, Serialize};
use std::{
fs::File,
io::{self, BufReader, BufWriter},
};
/// Rappresenta un Dungeon in stile RogueLike.\
/// In esso possiamo trovare dei piani generati casualmente
/// e dei giocatori che esplorano.
#[derive(Clone, Deserialize, Serialize)]
pub struct Dungeon {
floors: Vec<Floor>,
config: Config,
rng: Pcg32,
}
impl Dungeon {
/// Crea una nuova istanza di un dungeon con le configurazioni i default
pub fn new() -> Self {
Self::new_with(Config::default())
}
/// Crea una nuova istanza di un dungeon con le configurazioni passate in input
pub fn new_with(config: Config) -> Self {
let mut game = Self {
rng: Pcg32::seed_from_u64(config.game_seed),
floors: vec![],
config,
};
game.build_next_floor();
game
}
/// Carica il dungeon da un file.\
/// Il file deve essere formattato tramite json, altrimenti viene ritornato un errore.
pub fn load(filename: &str) -> io::Result<Self> {
let file = File::open(filename)?;
let reader = BufReader::new(file);
let dungeon: Self = serde_json::from_reader(reader)?;
Ok(dungeon)
}
/// Salva il dungeon corrente nel file indicato.\
/// Il salvataggio viene fatto tramite serializzazione JSON in modo che sia facile da vedere.\
/// Nel caso in cui ci siano problemi con I/O, viene ritornato un errore.
pub fn save(&mut self, filename: &str) -> io::Result<()> {
let file = File::create(filename)?;
let writer = BufWriter::new(file);
let _ = serde_json::to_writer_pretty(writer, self)?;
Ok(())
}
/// Aggiunge un giocatore al Dungeon, esso avrà le statistiche di base assegnate
/// 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<dyn Behavior>) {
let stats = &self.config.player_stats;
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(&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) -> &Floor {
let floors = self.floors.len() - 1;
let index = level.min(floors);
&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 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 tutte le entità del piano
/// - Modifica di piano di eventuali giocatori
pub fn compute_turn(&mut self) {
let moved: Option<Vec<Entity>> = 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
});
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
fn build_next_floor(&mut self) {
let floor_seed = self.rng.next_u64();
let floor_level = self.floors.len();
let generator = Generator::new(floor_seed, floor_level, &self.config);
let floor = generator.build_floor();
self.floors.push(floor);
}
}