Documenting 03
- added docs for all things - added methods for accessing entity stats - refactored some names
This commit is contained in:
@@ -39,11 +39,3 @@ pub mod generator;
|
||||
pub mod cell;
|
||||
pub mod config;
|
||||
pub mod entities;
|
||||
|
||||
pub fn start_game() {
|
||||
let mut game = game::Rogue::new();
|
||||
loop {
|
||||
println!("{}", game);
|
||||
game.compute_turn();
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,10 @@ use dyn_clone::{clone_trait_object, DynClone};
|
||||
use rand::Rng;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// Rappresentazione di una cella di spazio.\
|
||||
/// Essa ha diversi valori in base a cosa si può fare o meno su di essa.
|
||||
/// Nel caso in cui passi sopra una entià esiste un metodo entity_over che
|
||||
/// gestisce le varie casistiche.
|
||||
#[derive(Clone, Deserialize, Serialize)]
|
||||
pub enum Cell {
|
||||
Entance,
|
||||
@@ -13,6 +17,12 @@ pub enum Cell {
|
||||
}
|
||||
|
||||
impl Cell {
|
||||
/// Data una entità che passa sopra questa cella di spazio
|
||||
/// modifica la posizione e la fa tornare indietro nel caso sia un muro,
|
||||
/// nel caso di una cella speciale, applica l'effetto all'entità,
|
||||
/// e i tutti gli altri casi non fa nulla.\
|
||||
/// Il movimento tra piani tramite Exit e Entrance non è gestito in questa funzione
|
||||
/// data la complessità di muovere l'entità.
|
||||
pub fn entity_over(&mut self, entity: &mut Entity) {
|
||||
match self {
|
||||
Cell::Special(effect) => {
|
||||
@@ -30,13 +40,37 @@ impl Cell {
|
||||
}
|
||||
}
|
||||
|
||||
/// Trait che permette di implementare un effetto speciale di una
|
||||
/// cella di spazio.\
|
||||
/// Il trait è taggato con typetag in modo che possa essere utilizzato
|
||||
/// nella serializzazione e deserializzazione di serde.
|
||||
/// Esso permette di trasformare le implementazioni di Effect in una
|
||||
/// spiecie di Enum senza il bisogno di farlo manualmente.\
|
||||
/// Quello che viene richiesto è che, nell'implementazione di una
|
||||
/// struttura concreta di questo trait, venga messo sopra impl X for Effect:\
|
||||
/// #\[typetag::serde\]\
|
||||
/// \
|
||||
/// In questo modo si possono creare molteplici effetti che implementano
|
||||
/// questo trait senza il bisogno di avere un Enum con essi
|
||||
#[typetag::serde(tag = "type")]
|
||||
pub trait Effect: DynClone {
|
||||
/// Indica se l'effetto rimane nel terreno dopo la sua applicazione ad una entità.\
|
||||
/// Nel caso di true, l'effetto non verrà rimosso dal terreno,
|
||||
/// eltrimenti la cella dove si trova questo effetto diventerà Empty
|
||||
fn is_persistent(&self) -> bool;
|
||||
/// Applica l'effetto ad una entità.\
|
||||
/// L'effetto può essere di tutto a partire da un danno a qualcosa di più
|
||||
/// 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);
|
||||
}
|
||||
clone_trait_object!(Effect);
|
||||
|
||||
/// Permette di dare un danno istantaneo a qualunque entità ci passi sopra.\
|
||||
/// Una volta utilizzato verrà rimosso dal piano.\
|
||||
/// Nel caso in cui il danno sia negativo, l'entità verrà curata
|
||||
/// (sempre che la sua vita sia un valore positivo e non negativo)
|
||||
#[derive(Clone, Serialize, Deserialize)]
|
||||
pub struct InstantDamage(pub i32);
|
||||
#[typetag::serde]
|
||||
@@ -45,10 +79,14 @@ impl Effect for InstantDamage {
|
||||
false
|
||||
}
|
||||
fn apply_to(&self, entity: &mut Entity) {
|
||||
entity.health += self.0;
|
||||
entity.apply_damage(self.0);
|
||||
}
|
||||
}
|
||||
|
||||
/// Permettere di infliggere lo stato di confuzione ad una entità.\
|
||||
/// Esso ignora il successivo comando che verrà impartito all'entità
|
||||
/// con una probabilità del 50% e inserirà un movimento in una direzione casuale.\
|
||||
/// Come parametro si può passare per quanti turni l'effetto dura
|
||||
#[derive(Clone, Serialize, Deserialize)]
|
||||
pub struct Confusion(pub u8);
|
||||
#[typetag::serde]
|
||||
@@ -71,6 +109,10 @@ impl Effect for Confusion {
|
||||
}
|
||||
}
|
||||
|
||||
/// Permette di infliggere un danno nel tempo.\
|
||||
/// Similmente a InstantDamage, se il danno è negativo allora il personaggio verrà curato,
|
||||
/// sempre a patto che la sua vita sia un valore positivo.\
|
||||
/// L'effetto dura un determinato numero di turni.
|
||||
#[derive(Clone, Serialize, Deserialize)]
|
||||
pub struct TurnBasedDamage {
|
||||
time: u8,
|
||||
@@ -83,7 +125,7 @@ impl Effect for TurnBasedDamage {
|
||||
}
|
||||
fn apply_to(&self, entity: &mut Entity) {
|
||||
if self.time > 0 {
|
||||
entity.health += self.damage;
|
||||
entity.apply_damage(self.damage);
|
||||
entity.add_effect(Box::new(Self {
|
||||
time: self.time - 1,
|
||||
damage: self.damage,
|
||||
|
||||
@@ -5,11 +5,10 @@ use super::{
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::ops::Range;
|
||||
|
||||
/**
|
||||
* Struttura Config usata per definire il gioco, ha alcune cose utili
|
||||
* TODO sarebbe bello poterle prendere da file.
|
||||
*/
|
||||
|
||||
/// Struttura di configurazione per la creazione di un dungeon.\
|
||||
/// Ogni elemento indica un parametro per la generazione di un piano o di una entitità.\
|
||||
/// Esiste una implementazione di default di questa struttura che genera un dungeon
|
||||
/// molto semplice e standard.
|
||||
#[derive(Clone, Deserialize, Serialize)]
|
||||
pub struct Config {
|
||||
pub game_seed: u64,
|
||||
@@ -19,8 +18,15 @@ pub struct Config {
|
||||
pub effects: Vec<ConfigEffect>,
|
||||
pub entities_total: usize,
|
||||
pub entities: Vec<ConfigEntity>,
|
||||
pub player_stats: ConfigPlayer,
|
||||
}
|
||||
|
||||
/// Un effetto che si può trovare per terra nel dungeon.\
|
||||
/// La priorità indica quanto verrà spawnato l'effetto in media.\
|
||||
/// \
|
||||
/// Es. effetto A priorità 1 ed effetto B con priorità 2\
|
||||
/// Se in Config mettiamo 15 effetti per piano, allora avremo
|
||||
/// in media 10 A e 5 B per ogni piano.
|
||||
#[derive(Clone, Deserialize, Serialize)]
|
||||
pub struct ConfigEffect {
|
||||
pub floors: Range<usize>,
|
||||
@@ -28,6 +34,20 @@ pub struct ConfigEffect {
|
||||
pub priority: usize,
|
||||
}
|
||||
|
||||
/// Valori di base per le statistiche di un giocatore.\
|
||||
/// Esse verranno utilizzate quando un giocatore verrà creato.
|
||||
#[derive(Clone, Deserialize, Serialize)]
|
||||
pub struct ConfigPlayer {
|
||||
pub health: i32,
|
||||
pub attack: i32,
|
||||
}
|
||||
|
||||
/// Una entità che si può trovare in un piano nel dungeon.\
|
||||
/// La priorità indica quanto verrà spawnata l'entità in media.\
|
||||
/// \
|
||||
/// Es. entità A priorità 1 ed entità B con priorità 2\
|
||||
/// Se in Config mettiamo 15 entità per piano, allora avremo
|
||||
/// in media 10 A e 5 B per ogni piano.
|
||||
#[derive(Clone, Deserialize, Serialize)]
|
||||
pub struct ConfigEntity {
|
||||
pub floors: Range<usize>,
|
||||
@@ -35,6 +55,7 @@ pub struct ConfigEntity {
|
||||
pub decider: Box<dyn Decider>,
|
||||
pub health: i32,
|
||||
pub attack: i32,
|
||||
pub priority: usize,
|
||||
}
|
||||
|
||||
impl Default for Config {
|
||||
@@ -63,6 +84,10 @@ impl Default for Config {
|
||||
effects_total: 45,
|
||||
entities: vec![],
|
||||
entities_total: 0,
|
||||
player_stats: ConfigPlayer {
|
||||
health: 1000,
|
||||
attack: 100,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,45 +3,61 @@ use dyn_clone::{clone_trait_object, DynClone};
|
||||
use rand::Rng;
|
||||
use rand_pcg::Pcg32;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{collections::VecDeque, fmt::Display};
|
||||
use std::{collections::VecDeque, fmt::Display, mem};
|
||||
|
||||
/// Tupla nominata Position in modo che nel codice sia più chiaro a cosa serve.\
|
||||
/// È molto più facile capire a colpo d'occhio Position rispetto a (usize, usize)\
|
||||
/// I due valori sono la posizione sull'asse X e sull'asse Y\
|
||||
/// Il punto (0,0) si trova in basso a sinista.
|
||||
#[derive(Clone, Copy, Deserialize, Serialize)]
|
||||
pub struct Position(pub usize, pub usize);
|
||||
|
||||
/// Indica la direzione dove una entità sta guardando.\
|
||||
/// È possibile anche non guardare in nessuna direzione tramite None.
|
||||
#[derive(Clone, Deserialize, Serialize)]
|
||||
pub enum Direction {
|
||||
UP,
|
||||
DOWN,
|
||||
LEFT,
|
||||
RIGHT,
|
||||
NONE,
|
||||
Up,
|
||||
Down,
|
||||
Left,
|
||||
Right,
|
||||
None,
|
||||
}
|
||||
|
||||
impl Direction {
|
||||
/// Inverte la direzione attuale. (es. dx -> sx)\
|
||||
/// Questo metodo modifica la direzione inplace.
|
||||
pub fn invert(&mut self) {
|
||||
*self = match *self {
|
||||
Direction::UP => Direction::DOWN,
|
||||
Direction::DOWN => Direction::UP,
|
||||
Direction::RIGHT => Direction::LEFT,
|
||||
Direction::LEFT => Direction::RIGHT,
|
||||
_ => Direction::NONE,
|
||||
Direction::Up => Direction::Down,
|
||||
Direction::Down => Direction::Up,
|
||||
Direction::Right => Direction::Left,
|
||||
Direction::Left => Direction::Right,
|
||||
_ => Direction::None,
|
||||
};
|
||||
}
|
||||
/// Calcola la nuova posizione in base a dove si stà guardando.\
|
||||
/// La posizione viene modificata come se si stesse avanzando di una
|
||||
/// unità di spazio.\
|
||||
/// Es. (0,0) Up -> aumento la y di uno (0,1)
|
||||
pub fn move_from(&self, pos: Position) -> Position {
|
||||
match *self {
|
||||
Direction::UP => Position(pos.0, pos.1 + 1),
|
||||
Direction::DOWN => Position(pos.0, pos.1 - 1),
|
||||
Direction::RIGHT => Position(pos.0 + 1, pos.1),
|
||||
Direction::LEFT => Position(pos.0 - 1, pos.1),
|
||||
Direction::NONE => pos,
|
||||
Direction::Up => Position(pos.0, pos.1 + 1),
|
||||
Direction::Down => Position(pos.0, pos.1 - 1),
|
||||
Direction::Right => Position(pos.0 + 1, pos.1),
|
||||
Direction::Left => Position(pos.0 - 1, pos.1),
|
||||
Direction::None => pos,
|
||||
}
|
||||
}
|
||||
/// Restituisce una direzione casuale a partire da un generatore.\
|
||||
/// La direzione viene generata con una distribuzione uniforme, ovvero non
|
||||
/// c'è una direzione preferita o con più probabilità.
|
||||
pub fn random(rng: &mut Pcg32) -> Self {
|
||||
match rng.gen_range(0..4) {
|
||||
0 => Direction::UP,
|
||||
1 => Direction::DOWN,
|
||||
2 => Direction::LEFT,
|
||||
_ => Direction::RIGHT,
|
||||
match rng.gen_range(0..5) {
|
||||
0 => Direction::Up,
|
||||
1 => Direction::Down,
|
||||
2 => Direction::Left,
|
||||
3 => Direction::Right,
|
||||
_ => Direction::None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -49,17 +65,18 @@ impl Direction {
|
||||
impl Display for Direction {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let c = match self {
|
||||
Self::UP => '▲',
|
||||
Self::DOWN => '▼',
|
||||
Self::LEFT => '◄',
|
||||
Self::RIGHT => '►',
|
||||
Self::NONE => '■',
|
||||
Self::Up => '▲',
|
||||
Self::Down => '▼',
|
||||
Self::Left => '◄',
|
||||
Self::Right => '►',
|
||||
Self::None => '■',
|
||||
};
|
||||
|
||||
write!(f, "{}", c)
|
||||
}
|
||||
}
|
||||
|
||||
/// Rappresenta una entità all'interno del dungeon.
|
||||
#[derive(Clone, Deserialize, Serialize)]
|
||||
pub struct Entity {
|
||||
name: String,
|
||||
@@ -69,40 +86,98 @@ pub struct Entity {
|
||||
pub buffer: Action,
|
||||
pub position: Position,
|
||||
pub direction: Direction,
|
||||
pub health: i32,
|
||||
health_max: i32,
|
||||
health: i32,
|
||||
attack: i32,
|
||||
}
|
||||
|
||||
impl Entity {
|
||||
pub fn new(name: String, decider: Box<dyn Decider>, mut floor: FloorPtr) -> Self {
|
||||
/// Costruttore che crea una nuova entità a partire dal suo nome, vita, danno di attacco,
|
||||
/// il decisore che permette di muoversi (giocatore o IA) e il piano in cui si trova.\
|
||||
/// La posizione sarà all'entrata del piano (o in una cella vicina nel caso ci siano altre entità sopra),
|
||||
/// non avrà effetti, azioni o una direzione in particolare.
|
||||
pub fn new(
|
||||
name: String,
|
||||
health: i32,
|
||||
attack: i32,
|
||||
decider: Box<dyn Decider>,
|
||||
mut floor: FloorPtr,
|
||||
) -> Self {
|
||||
let position = floor.get().get_entrance();
|
||||
Self {
|
||||
name,
|
||||
floor,
|
||||
decider,
|
||||
position,
|
||||
attack,
|
||||
health,
|
||||
health_max: health,
|
||||
buffer: Action::DoNothing,
|
||||
effects: VecDeque::new(),
|
||||
direction: Direction::NONE,
|
||||
attack: 100,
|
||||
health: 100,
|
||||
direction: Direction::None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Aggiunge l'effetto passato in input all'entità.\
|
||||
/// Questo non viene calcolato immediatamente, ma solo quando si chiama la
|
||||
/// funzione update.\
|
||||
/// È stato fatto in questo modo dato che ci possono essere effetti che ne aggiungono altri
|
||||
/// e quindi si farebbe una ricorsione infinita.
|
||||
pub fn add_effect(&mut self, effect: Box<dyn Effect>) {
|
||||
self.effects.push_back(effect);
|
||||
}
|
||||
|
||||
/// Indica se l'entità è considerata ancora in gioco o meno.\
|
||||
/// Per far si che l'entità non sia più in gioco bisobna far arrivare la vita a 0.
|
||||
/// Nota: una entità con vita negativa è considerata "viva"
|
||||
pub fn is_alive(&self) -> bool {
|
||||
self.health != 0
|
||||
}
|
||||
|
||||
/// Restituisce il valore della vita dell'entità.\
|
||||
pub fn get_health(&self) -> i32 {
|
||||
self.health
|
||||
}
|
||||
|
||||
/// Applica il valore inserito come danno alla vita.\
|
||||
/// Nel caso in cui il danno sia negativo allora verrà interpretato come cura.\
|
||||
/// Nel caso in cui la vita sia negativa la logica sarà inversa.\
|
||||
/// Il danno/cura non potrà comunque superare lo 0 o la vita massima.
|
||||
pub fn apply_damage(&mut self, damage: i32) {
|
||||
let health = self.health - damage;
|
||||
self.health = if self.health_max > 0 {
|
||||
health.min(self.health_max).max(0)
|
||||
} else {
|
||||
health.max(self.health_max).min(0)
|
||||
};
|
||||
}
|
||||
|
||||
/// 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();
|
||||
}
|
||||
pub fn update(&mut self) {
|
||||
self.compute_action();
|
||||
self.compute_effects();
|
||||
|
||||
/// Permette all'entità di fare un'azione e successivamente calcola
|
||||
/// tutti gli effetti che devono essere applicati ad essa.\
|
||||
/// Nel caso in cui l'entità non sia più in vita questo metodo ritornerà false
|
||||
/// e non permetterà all'entità di fare un 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();
|
||||
return true;
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
/// calcola gli effetti e li applica all'entità.
|
||||
fn compute_effects(&mut self) {
|
||||
let total = self.effects.len(); // len could change
|
||||
for _ in 0..total {
|
||||
@@ -111,24 +186,52 @@ impl Entity {
|
||||
}
|
||||
}
|
||||
}
|
||||
fn compute_action(&mut self) {
|
||||
let action = self.decider.get_next_action();
|
||||
match self.buffer {
|
||||
Action::DoNothing => action.apply(self),
|
||||
_ => (),
|
||||
}
|
||||
self.buffer = Action::DoNothing;
|
||||
/// prende una decisione e applica l'azione da fare
|
||||
/// L'azione compiuta viene restituita, altrimenti None
|
||||
fn compute_action(&mut self) -> Option<Action> {
|
||||
let action = self.decider.get_next_action()?;
|
||||
let action = match self.buffer {
|
||||
Action::DoNothing => action,
|
||||
_ => mem::replace(&mut self.buffer, Action::DoNothing),
|
||||
};
|
||||
|
||||
let result = Some(action.clone());
|
||||
action.apply(self);
|
||||
result
|
||||
}
|
||||
|
||||
/// Metodo statico per l'update e l'eventuale eliminazione di entità da un vettore.
|
||||
/// Le entità rimosse sono quelle che non riescono a fare l'update o che eventualmente
|
||||
/// non sono più in vita
|
||||
pub fn update_from_vec(entities: &mut Vec<Entity>) {
|
||||
let to_remove: Vec<usize> = entities
|
||||
.iter_mut()
|
||||
.enumerate()
|
||||
.filter_map(|(i, entity)| if entity.update() { Some(i) } else { None })
|
||||
.rev()
|
||||
.collect();
|
||||
to_remove.iter().for_each(|i| {
|
||||
entities.remove(*i);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// Azione che una qualsiasi entità può fare.
|
||||
/// L'azione DoNothing permette all'entità di saltare il turno nel caso in cui sia utile.
|
||||
#[derive(Clone, Deserialize, Serialize)]
|
||||
pub enum Action {
|
||||
Move(Direction),
|
||||
// attack
|
||||
//Attack(Direction),
|
||||
DoNothing,
|
||||
}
|
||||
|
||||
impl Action {
|
||||
/// Applica l'azione all'entità passata.\
|
||||
/// Dopo la chiamata di questa funzione l'azione non sarà più disponibile.\
|
||||
/// Per ogni tipo di azione l'entità viene modificata opportunamente.\
|
||||
/// \
|
||||
/// 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) {
|
||||
match self {
|
||||
Action::DoNothing => {}
|
||||
@@ -146,17 +249,40 @@ impl Action {
|
||||
}
|
||||
}
|
||||
|
||||
clone_trait_object!(Decider);
|
||||
/// Questo trait è molto importante per le entità perchè è responsabile del loro comportamento.\
|
||||
/// Con questo trait si possono creare diversi comportamenti semplicemente implementandolo
|
||||
/// e utilizzandolo come parametro nella generazione di una entità.\
|
||||
/// \
|
||||
/// Il trait è taggato con typetag in modo che possa essere utilizzato
|
||||
/// nella serializzazione e deserializzazione di serde.
|
||||
/// Esso permette di trasformare le implementazioni di Decider in una
|
||||
/// spiecie di Enum senza il bisogno di farlo manualmente.\
|
||||
/// Quello che viene richiesto è che, nell'implementazione di una
|
||||
/// struttura concreta di questo trait, venga messo sopra impl X for Decider:\
|
||||
/// #\[typetag::serde\]\
|
||||
/// \
|
||||
/// In questo modo si possono creare molteplici comoprtamenti che implementano
|
||||
/// questo trait senza il bisogno di avere un Enum con essi
|
||||
#[typetag::serde(tag = "type")]
|
||||
pub trait Decider: DynClone {
|
||||
fn get_next_action(&self) -> Action;
|
||||
/// Genera una azione che poi verrà usata per l'entità associata a questo Decider.\
|
||||
/// L'azione può essere generata in qualunque modo: casuale, sempre la stessa,
|
||||
/// tramite interazione con console, o tramite una connessione ad un client.\
|
||||
/// \
|
||||
/// Nel caso in cui venga restituito None come valore, l'entità verrà rimossa dal gioco.\
|
||||
/// Questo viene fatto in modo che si possa avere una possibilità di rimozione del giocatore,
|
||||
/// ma anche una possibilità che alcune entità rare possano sparire.
|
||||
fn get_next_action(&self) -> Option<Action>;
|
||||
}
|
||||
clone_trait_object!(Decider);
|
||||
|
||||
/// Semplice implementazione di un possibile comportamento di una entità.\
|
||||
/// In questo caso l'entità resterà immobile nel punto in cui si trova per sempre.
|
||||
#[derive(Clone, Serialize, Deserialize)]
|
||||
pub struct Immovable;
|
||||
#[typetag::serde]
|
||||
impl Decider for Immovable {
|
||||
fn get_next_action(&self) -> Action {
|
||||
Action::DoNothing
|
||||
fn get_next_action(&self) -> Option<Action> {
|
||||
Some(Action::DoNothing)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,19 +9,30 @@ use std::{
|
||||
rc::Rc,
|
||||
};
|
||||
|
||||
/// Tupla creata per poter implementare qualche metodo sulla struttura Rc<RefCell<Floor>>\
|
||||
/// 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<RefCell<Floor>>);
|
||||
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<Entity>, grid: Vec<Vec<Cell>>) -> Self {
|
||||
Self(Rc::new(RefCell::new(Floor::new(
|
||||
level, rng, entities, grid,
|
||||
))))
|
||||
}
|
||||
|
||||
/// Permette di prendere il valore puntato al piano.
|
||||
pub fn get(&mut self) -> RefMut<Floor> {
|
||||
self.0.borrow_mut()
|
||||
}
|
||||
}
|
||||
|
||||
/// Indica un piano del dungeon, in essa si possono trovare le celle in cui si
|
||||
/// cammina e le entità che abitano il piano.\
|
||||
/// Per poter accedere a questa struttura è necessario utilizzare FloorPtr e fare get()
|
||||
#[derive(Clone, Deserialize, Serialize)]
|
||||
pub struct Floor {
|
||||
level: usize,
|
||||
@@ -31,6 +42,7 @@ pub struct Floor {
|
||||
}
|
||||
|
||||
impl Floor {
|
||||
/// Crea un piano secondo i parametri indicati
|
||||
fn new(level: usize, rng: Pcg32, entities: Vec<Entity>, grid: Vec<Vec<Cell>>) -> Self {
|
||||
Self {
|
||||
level,
|
||||
@@ -40,15 +52,25 @@ impl Floor {
|
||||
}
|
||||
}
|
||||
|
||||
/// Restituisce il livello di profondità del piano
|
||||
pub fn get_level(&self) -> usize {
|
||||
self.level
|
||||
}
|
||||
|
||||
/// Restituisce il generatore di numeri casuali utilizzato per qualunque cosa
|
||||
/// inerente al piano (generazione di entità, applicazione di effetti...)
|
||||
pub fn get_rng(&mut self) -> &mut Pcg32 {
|
||||
&mut self.rng
|
||||
}
|
||||
|
||||
/// Restituisce la cella nella posizione indicata.\
|
||||
/// Con essa si può fare cio che si vuole, e quindi anche modificarla.
|
||||
pub fn get_cell(&mut self, pos: Position) -> &mut Cell {
|
||||
&mut self.grid[pos.0][pos.1]
|
||||
}
|
||||
|
||||
/// Restituisce la posizione dell'entrata del piano.\
|
||||
/// Utile come spawn per quando i giocatori arrivano al piano.
|
||||
pub fn get_entrance(&mut self) -> Position {
|
||||
self.grid
|
||||
.iter()
|
||||
@@ -65,9 +87,8 @@ impl Floor {
|
||||
.expect("Entrance of the floor should be inside the grid!")
|
||||
}
|
||||
|
||||
/// Fa l'update di tutte le entità e rimuove eventualmente quelle non più in vita
|
||||
pub fn update_entities(&mut self) {
|
||||
for entity in &mut self.entities {
|
||||
entity.update();
|
||||
}
|
||||
Entity::update_from_vec(&mut self.entities);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,29 +8,25 @@ use super::{
|
||||
use rand::{RngCore, SeedableRng};
|
||||
use rand_pcg::Pcg32;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt::Display;
|
||||
|
||||
/**
|
||||
* Struttura del gioco generico che implementa un RogueLike.
|
||||
*/
|
||||
/// 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 Rogue {
|
||||
pub struct Dungeon {
|
||||
floors: Vec<FloorPtr>,
|
||||
rng: Pcg32,
|
||||
config: Config,
|
||||
players: Vec<Entity>,
|
||||
}
|
||||
|
||||
impl Display for Rogue {
|
||||
fn fmt(&self, _f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
impl Rogue {
|
||||
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),
|
||||
@@ -41,33 +37,41 @@ impl Rogue {
|
||||
game.build_next_floor();
|
||||
game
|
||||
}
|
||||
|
||||
/// 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) {
|
||||
let floor = self.floors[0].clone();
|
||||
let decider = Box::new(Immovable);
|
||||
let entity = Entity::new(name, decider, floor);
|
||||
let stats = &self.config.player_stats;
|
||||
let entity = Entity::new(name, stats.health, stats.attack, decider, floor);
|
||||
self.players.push(entity);
|
||||
}
|
||||
|
||||
/// 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 {
|
||||
let floors = self.floors.len() - 1;
|
||||
let index = level.min(floors);
|
||||
self.floors[index].clone()
|
||||
}
|
||||
pub 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);
|
||||
}
|
||||
|
||||
/// Funzione principale del dungeon.\
|
||||
/// In essa viene fatto fare l'update ai giocatori e ad ogni piano.
|
||||
/// In generale l'algoritmo è il seguente:\
|
||||
/// - 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
|
||||
/// - Modifica di piano di eventuali giocatori
|
||||
pub fn compute_turn(&mut self) {
|
||||
let mut update_floors = vec![false; self.floors.len()];
|
||||
let mut change_floors = vec![0; self.players.len()];
|
||||
|
||||
Entity::update_from_vec(&mut self.players);
|
||||
self.players.iter_mut().enumerate().for_each(|(i, player)| {
|
||||
let mut floor = player.get_floor();
|
||||
let mut floor = floor.get();
|
||||
|
||||
player.update();
|
||||
update_floors[floor.get_level()] = true;
|
||||
if let Cell::Exit = floor.get_cell(player.position) {
|
||||
change_floors[i] = floor.get_level() + 1;
|
||||
@@ -89,6 +93,16 @@ impl Rogue {
|
||||
player.set_floor(floor);
|
||||
});
|
||||
}
|
||||
|
||||
/// 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);
|
||||
}
|
||||
/// 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() {
|
||||
|
||||
@@ -7,6 +7,12 @@ use rand::{Rng, SeedableRng};
|
||||
use rand_pcg::Pcg32;
|
||||
use std::ops::Range;
|
||||
|
||||
/// Generatore del gioco che può creare dei piani del dungeon.
|
||||
/// Idealmente questo generatore si comporta come il pattern Factory.
|
||||
/// Per far si che funzioni ha bisongo di un seed per la generazione del piano
|
||||
/// verrà utilizzato poi dal piano stesso per eventuali altri calcoli.
|
||||
/// Inoltre ad esso viene passato una struttura di config che permette
|
||||
/// di scegliere meglio come poter generare il piano.
|
||||
pub struct Generator {
|
||||
pub rng: Pcg32,
|
||||
pub level: usize,
|
||||
@@ -20,6 +26,8 @@ pub struct Generator {
|
||||
}
|
||||
|
||||
impl Generator {
|
||||
/// Costruttore standard di un generatore
|
||||
/// il generatore creato avrà come entità quelle registrate nella configurazione
|
||||
pub fn new(floor_seed: u64, floor_level: usize, config: &Config) -> Self {
|
||||
let mut rand_pcg = Pcg32::seed_from_u64(floor_seed);
|
||||
let floor_size = rand_pcg.gen_range(config.floor_size.clone());
|
||||
@@ -29,17 +37,19 @@ impl Generator {
|
||||
size: floor_size,
|
||||
size_rooms: config.room_size.clone(),
|
||||
effects_total: config.effects_total,
|
||||
effects: Self::clone_vec_filter(&config.effects, |val| {
|
||||
effects: Self::vec_filter(&config.effects, |val| {
|
||||
val.floors.contains(&floor_level)
|
||||
}),
|
||||
entities_total: config.entities_total,
|
||||
entities: Self::clone_vec_filter(&config.entities, |val| {
|
||||
entities: Self::vec_filter(&config.entities, |val| {
|
||||
val.floors.contains(&floor_level)
|
||||
}),
|
||||
grid: Self::grid_with_only(floor_size, Cell::Wall),
|
||||
}
|
||||
}
|
||||
|
||||
/// 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 {
|
||||
for x in 1..(self.size - 1) {
|
||||
for y in 1..(self.size - 1) {
|
||||
@@ -51,15 +61,19 @@ impl Generator {
|
||||
self.rand_place_effects();
|
||||
FloorPtr::new(self.level, self.rng, vec![], self.grid)
|
||||
}
|
||||
|
||||
/// TODO
|
||||
pub fn build_floor_catacomb(mut self) -> Floor {
|
||||
// init to WALLS
|
||||
// reserve some cells for rooms ??
|
||||
todo!()
|
||||
}
|
||||
|
||||
/// TODO
|
||||
fn build_rooms(&mut self) {
|
||||
todo!()
|
||||
}
|
||||
/// crea una stanza in un punto casuale del piano e restituisce il suo boundingbox
|
||||
fn rand_build_room(&mut self) -> (usize, usize, usize, usize) {
|
||||
loop {
|
||||
let (x, y) = self.rand_2d(0..self.size);
|
||||
@@ -76,7 +90,8 @@ impl Generator {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// piazza gli effetti della confgurazione in modo casuale su tutto il piano.\
|
||||
/// essi vengono piazzati solamente sulle celle Empty
|
||||
fn rand_place_effects(&mut self) {
|
||||
for _ in 0..self.effects_total {
|
||||
let index = self.rng.gen_range(0..self.effects.len());
|
||||
@@ -85,6 +100,8 @@ impl Generator {
|
||||
self.rand_place_cell(cell);
|
||||
}
|
||||
}
|
||||
/// piazza una cella in un punto casuale del piano.\
|
||||
/// il metodo contiuna a provare a piazzare la cella finche non trova una cella Empty.
|
||||
fn rand_place_cell(&mut self, cell: Cell) -> (usize, usize) {
|
||||
loop {
|
||||
let (x, y) = self.rand_2d(0..self.size);
|
||||
@@ -94,19 +111,21 @@ impl Generator {
|
||||
}
|
||||
}
|
||||
}
|
||||
/// genera una tupla di due valori randomici nel range passato in input
|
||||
fn rand_2d(&mut self, range: Range<usize>) -> (usize, usize) {
|
||||
let x = self.rng.gen_range(range.clone());
|
||||
let y = self.rng.gen_range(range);
|
||||
(x, y)
|
||||
}
|
||||
|
||||
fn clone_vec_filter<T: Clone>(original: &Vec<T>, filter: impl Fn(&T) -> bool) -> Vec<T> {
|
||||
/// crea una vista del vettore passato in input dopo aver applicato la funzione di filtro
|
||||
fn vec_filter<T: Clone>(original: &Vec<T>, filter: impl Fn(&T) -> bool) -> Vec<T> {
|
||||
original
|
||||
.clone()
|
||||
.into_iter()
|
||||
.filter_map(|val| if filter(&val) { Some(val) } else { None })
|
||||
.collect()
|
||||
}
|
||||
/// crea un campo con solamente la cella specificata clonata su tutto di esso
|
||||
fn grid_with_only(size: usize, cell: Cell) -> Vec<Vec<Cell>> {
|
||||
vec![vec![cell; size]; size]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user