Display
- added Debug for many structs - added you_died to behavior - modified FloorView to iterface better - moved many traits impl to mod.rs
This commit is contained in:
@@ -1,41 +0,0 @@
|
|||||||
|
|
||||||
/** Es.3
|
|
||||||
* Implementare una libreria che permetta di realizzare il seguente gioco.
|
|
||||||
* Il Campo di gioco e' una matrice n x n di celle le celle sui 4 lati sono dei muri e all'interno le celle possono essere
|
|
||||||
* - vuote
|
|
||||||
* - contenere cibo (un intero positivo)
|
|
||||||
* - contenere un veleno (un intero positivo)
|
|
||||||
*
|
|
||||||
* Un Giocatore si muove in questa matrice iniziando da una posizione casuale. Il giocatore ha
|
|
||||||
* - Direzione in cui si muove: Su, Giu', Destra, Sinistra
|
|
||||||
* - Posizione nella matrice
|
|
||||||
* - una forza (un intero positivo)
|
|
||||||
*
|
|
||||||
* Quando si muove avanza di una posizione nella direzione in cui il giocatore si muove. Una Configurazione e'
|
|
||||||
* un campo di gioco, e un giocatore in una posizione del campo per questa struttura implementate il trait Display
|
|
||||||
*
|
|
||||||
* Il gioco inizia con una configurazione in cui nella matrice ci sono m caselle con cibo e m con veleno (in posizioni casuali), un giocatore in una cella libera e un numero massimo di mosse.
|
|
||||||
* Ad ogni iterazione: Si lancia una moneta (Testa o Croce) se
|
|
||||||
* - Testa il giocatore si muove di una posizione nella direzione in cui si sta muovendo
|
|
||||||
* - altrimenti sceglie casualmente una dell 4 direzioni e fa un passo in quella direzione.
|
|
||||||
*
|
|
||||||
* Se la cella in cui si finisce
|
|
||||||
* contiene cibo, si aggiunge la quantita' di cibo alla forza
|
|
||||||
* contiene veleno, si decrementa la quantita' di veleno dalla forza
|
|
||||||
* e' un muro il giocatore rimbalza, cioe' resta nella stessa posizione ma cambia la sua direzione nella direzione opposta.
|
|
||||||
*
|
|
||||||
* Il gioco finisce quando
|
|
||||||
* - il giocatore finisce la forza (cioe' questa diventerebbe un valore <=0) e in questo caso PERDE
|
|
||||||
* - raggiunge il numero massimo di mosse nel qual caso VINCE
|
|
||||||
*
|
|
||||||
* Per n, m, le quantità iniziali dei vari elementi (elemento, cibo, forza) e il numero massimo di mosse usate variabili che possano essere inserite dall'utente.
|
|
||||||
* Se volete potete anche cambiare le regole del gioco.
|
|
||||||
* Mettere main e definizioni in files separati (le definizioni in uno o più files) e scrivete i test in una directory a parte.
|
|
||||||
*/
|
|
||||||
|
|
||||||
pub mod floor;
|
|
||||||
pub mod game;
|
|
||||||
pub mod generator;
|
|
||||||
pub mod cell;
|
|
||||||
pub mod config;
|
|
||||||
pub mod entities;
|
|
||||||
@@ -8,7 +8,7 @@ use std::fmt::Display;
|
|||||||
/// Essa ha diversi valori in base a cosa si può fare o meno su di essa.
|
/// 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
|
/// Nel caso in cui passi sopra una entià esiste un metodo entity_over che
|
||||||
/// gestisce le varie casistiche.
|
/// gestisce le varie casistiche.
|
||||||
#[derive(Clone, Deserialize, Serialize)]
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
pub enum Cell {
|
pub enum Cell {
|
||||||
Entance,
|
Entance,
|
||||||
Exit,
|
Exit,
|
||||||
@@ -71,7 +71,7 @@ impl Display for Cell {
|
|||||||
/// In questo modo si possono creare molteplici effetti che implementano
|
/// In questo modo si possono creare molteplici effetti che implementano
|
||||||
/// questo trait senza il bisogno di avere un Enum con essi
|
/// questo trait senza il bisogno di avere un Enum con essi
|
||||||
#[typetag::serde(tag = "type")]
|
#[typetag::serde(tag = "type")]
|
||||||
pub trait Effect: DynClone {
|
pub trait Effect: DynClone + core::fmt::Debug {
|
||||||
/// Indica se l'effetto rimane nel terreno dopo la sua applicazione ad una entità.\
|
/// 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,
|
/// Nel caso di true, l'effetto non verrà rimosso dal terreno,
|
||||||
/// eltrimenti la cella dove si trova questo effetto diventerà Empty
|
/// eltrimenti la cella dove si trova questo effetto diventerà Empty
|
||||||
@@ -89,7 +89,7 @@ clone_trait_object!(Effect);
|
|||||||
/// Una volta utilizzato verrà rimosso dal piano.\
|
/// Una volta utilizzato verrà rimosso dal piano.\
|
||||||
/// Nel caso in cui il danno sia negativo, l'entità verrà curata
|
/// Nel caso in cui il danno sia negativo, l'entità verrà curata
|
||||||
/// (sempre che la sua vita sia un valore positivo e non negativo)
|
/// (sempre che la sua vita sia un valore positivo e non negativo)
|
||||||
#[derive(Clone, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
pub struct InstantDamage(pub i32);
|
pub struct InstantDamage(pub i32);
|
||||||
#[typetag::serde]
|
#[typetag::serde]
|
||||||
impl Effect for InstantDamage {
|
impl Effect for InstantDamage {
|
||||||
@@ -105,7 +105,7 @@ impl Effect for InstantDamage {
|
|||||||
/// Esso ignora il successivo comando che verrà impartito all'entità
|
/// Esso ignora il successivo comando che verrà impartito all'entità
|
||||||
/// con una probabilità del 50% e inserirà un movimento in una direzione casuale.\
|
/// con una probabilità del 50% e inserirà un movimento in una direzione casuale.\
|
||||||
/// Come parametro si può passare per quanti turni l'effetto dura
|
/// Come parametro si può passare per quanti turni l'effetto dura
|
||||||
#[derive(Clone, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
pub struct Confusion(pub u8);
|
pub struct Confusion(pub u8);
|
||||||
#[typetag::serde]
|
#[typetag::serde]
|
||||||
impl Effect for Confusion {
|
impl Effect for Confusion {
|
||||||
@@ -128,7 +128,7 @@ impl Effect for Confusion {
|
|||||||
/// Similmente a InstantDamage, se il danno è negativo allora il personaggio verrà curato,
|
/// Similmente a InstantDamage, se il danno è negativo allora il personaggio verrà curato,
|
||||||
/// sempre a patto che la sua vita sia un valore positivo.\
|
/// sempre a patto che la sua vita sia un valore positivo.\
|
||||||
/// L'effetto dura un determinato numero di turni.
|
/// L'effetto dura un determinato numero di turni.
|
||||||
#[derive(Clone, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
pub struct TurnBasedDamage {
|
pub struct TurnBasedDamage {
|
||||||
time: u8,
|
time: u8,
|
||||||
damage: i32,
|
damage: i32,
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ use std::ops::Range;
|
|||||||
/// Struttura di configurazione per la creazione di un dungeon.\
|
/// Struttura di configurazione per la creazione di un dungeon.\
|
||||||
/// Ogni elemento indica un parametro per la generazione di un piano o di una entitità.\
|
/// 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 standard.
|
/// Esiste una implementazione di default di questa struttura che genera un dungeon standard.
|
||||||
#[derive(Clone, Deserialize, Serialize)]
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
pub game_seed: u64,
|
pub game_seed: u64,
|
||||||
pub maze_generation: ConfigMaze,
|
pub maze_generation: ConfigMaze,
|
||||||
@@ -25,7 +25,7 @@ pub struct Config {
|
|||||||
/// *straight_percentage* indica da 0 a 100 quanta percentuale c'è che un corridioio, quando viene generato
|
/// *straight_percentage* indica da 0 a 100 quanta percentuale c'è che un corridioio, quando viene generato
|
||||||
/// rimanga dritto o viri.\
|
/// rimanga dritto o viri.\
|
||||||
/// *dead_ends* indica quanti corridoi che non portano a nulla devono esserci alla fine della generazione.
|
/// *dead_ends* indica quanti corridoi che non portano a nulla devono esserci alla fine della generazione.
|
||||||
#[derive(Clone, Deserialize, Serialize)]
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
pub struct ConfigMaze {
|
pub struct ConfigMaze {
|
||||||
pub floor_size: Range<usize>,
|
pub floor_size: Range<usize>,
|
||||||
pub room_size: Range<usize>,
|
pub room_size: Range<usize>,
|
||||||
@@ -40,16 +40,16 @@ pub struct ConfigMaze {
|
|||||||
/// Es. effetto A priorità 1 ed effetto B con priorità 2\
|
/// Es. effetto A priorità 1 ed effetto B con priorità 2\
|
||||||
/// Se in Config mettiamo 15 effetti per piano, allora avremo
|
/// Se in Config mettiamo 15 effetti per piano, allora avremo
|
||||||
/// in media 10 A e 5 B per ogni piano.
|
/// in media 10 A e 5 B per ogni piano.
|
||||||
#[derive(Clone, Deserialize, Serialize)]
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
pub struct ConfigEffect {
|
pub struct ConfigEffect {
|
||||||
pub floors: Range<usize>,
|
pub floors: Range<usize>,
|
||||||
pub effect: Box<dyn Effect>,
|
pub effect: Box<dyn Effect>,
|
||||||
pub priority: usize,
|
pub priority: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Valori di base per le statistiche di un giocatore.\
|
/// Valori di base per le statistiche di un giocatore.\
|
||||||
/// Esse verranno utilizzate quando un giocatore verrà creato.
|
/// Esse verranno utilizzate quando un giocatore verrà creato.
|
||||||
#[derive(Clone, Deserialize, Serialize)]
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
pub struct ConfigPlayer {
|
pub struct ConfigPlayer {
|
||||||
pub health: i32,
|
pub health: i32,
|
||||||
pub attack: i32,
|
pub attack: i32,
|
||||||
@@ -61,14 +61,14 @@ pub struct ConfigPlayer {
|
|||||||
/// Es. entità A priorità 1 ed entità B con priorità 2\
|
/// Es. entità A priorità 1 ed entità B con priorità 2\
|
||||||
/// Se in Config mettiamo 15 entità per piano, allora avremo
|
/// Se in Config mettiamo 15 entità per piano, allora avremo
|
||||||
/// in media 10 A e 5 B per ogni piano.
|
/// in media 10 A e 5 B per ogni piano.
|
||||||
#[derive(Clone, Deserialize, Serialize)]
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
pub struct ConfigEntity {
|
pub struct ConfigEntity {
|
||||||
pub floors: Range<usize>,
|
pub floors: Range<usize>,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub decider: Box<dyn Behavior>,
|
pub decider: Box<dyn Behavior>,
|
||||||
pub health: i32,
|
pub health: i32,
|
||||||
pub attack: i32,
|
pub attack: i32,
|
||||||
pub priority: usize,
|
pub priority: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Config {
|
impl Default for Config {
|
||||||
@@ -103,8 +103,8 @@ impl Default for Config {
|
|||||||
entities: vec![],
|
entities: vec![],
|
||||||
entities_total: 0,
|
entities_total: 0,
|
||||||
player_stats: ConfigPlayer {
|
player_stats: ConfigPlayer {
|
||||||
health: 1000,
|
health: 100,
|
||||||
attack: 100,
|
attack: 10,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ use dyn_clone::{clone_trait_object, DynClone};
|
|||||||
use rand::Rng;
|
use rand::Rng;
|
||||||
use rand_pcg::Pcg32;
|
use rand_pcg::Pcg32;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::{collections::VecDeque, fmt::Display, io::Write, mem};
|
use std::{collections::VecDeque, fmt::Display, mem};
|
||||||
|
|
||||||
/// Tupla nominata Position in modo che nel codice sia più chiaro a cosa serve.\
|
/// 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)\
|
/// È molto più facile capire a colpo d'occhio Position rispetto a (usize, usize)\
|
||||||
@@ -17,7 +17,7 @@ pub struct Position(pub usize, pub usize);
|
|||||||
|
|
||||||
/// Indica la direzione dove una entità sta guardando.\
|
/// Indica la direzione dove una entità sta guardando.\
|
||||||
/// È possibile anche non guardare in nessuna direzione tramite None.
|
/// È possibile anche non guardare in nessuna direzione tramite None.
|
||||||
#[derive(PartialEq, Eq, Hash, Clone, Copy, Deserialize, Serialize)]
|
#[derive(PartialEq, Eq, Hash, Clone, Copy, Debug, Deserialize, Serialize)]
|
||||||
pub enum Direction {
|
pub enum Direction {
|
||||||
Up,
|
Up,
|
||||||
Down,
|
Down,
|
||||||
@@ -85,7 +85,7 @@ impl Display for Direction {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Rappresenta una entità all'interno del dungeon.
|
/// Rappresenta una entità all'interno del dungeon.
|
||||||
#[derive(Clone, Deserialize, Serialize)]
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
pub struct Entity {
|
pub struct Entity {
|
||||||
name: String,
|
name: String,
|
||||||
effects: VecDeque<Box<dyn Effect>>,
|
effects: VecDeque<Box<dyn Effect>>,
|
||||||
@@ -210,7 +210,7 @@ impl Display for Entity {
|
|||||||
|
|
||||||
/// Azione che una qualsiasi entità può fare.
|
/// Azione che una qualsiasi entità può fare.
|
||||||
/// L'azione DoNothing permette all'entità di saltare il turno nel caso in cui sia utile.
|
/// L'azione DoNothing permette all'entità di saltare il turno nel caso in cui sia utile.
|
||||||
#[derive(Clone, Deserialize, Serialize)]
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
pub enum Action {
|
pub enum Action {
|
||||||
Move(Direction),
|
Move(Direction),
|
||||||
//Attack(Direction),
|
//Attack(Direction),
|
||||||
@@ -253,12 +253,16 @@ impl Action {
|
|||||||
/// In questo modo si possono creare molteplici comoprtamenti che implementano
|
/// In questo modo si possono creare molteplici comoprtamenti che implementano
|
||||||
/// questo trait senza il bisogno di avere un Enum con essi
|
/// questo trait senza il bisogno di avere un Enum con essi
|
||||||
#[typetag::serde(tag = "type")]
|
#[typetag::serde(tag = "type")]
|
||||||
pub trait Behavior: DynClone {
|
pub trait Behavior: DynClone + core::fmt::Debug {
|
||||||
/// In questo metodo viene passata una struttura che contiene una rappresentazione del
|
/// In questo metodo viene passata una struttura che contiene una rappresentazione del
|
||||||
/// piano semplice, avente solo delle informazioni parziali.\
|
/// piano semplice, avente solo delle informazioni parziali.\
|
||||||
/// Questo serve a mostrare eventualmente delle possibili informazioni all'utente
|
/// Questo serve a mostrare eventualmente delle possibili informazioni all'utente
|
||||||
/// o di registrare dei valori per l'algoritmo di generazione delle azioni.
|
/// o di registrare dei valori per l'algoritmo di generazione delle azioni.
|
||||||
fn update(&self, floor: FloorView);
|
fn update(&self, floor: FloorView);
|
||||||
|
/// Funzione che viene richiamata quando l'entità muore.\
|
||||||
|
/// I parametri servono a far vedere un'ultima volta i dati del piano corrente all'entità
|
||||||
|
/// in modo che possa eventualmente fare ulteriori calcoli.
|
||||||
|
fn you_died(&self, floor: FloorView);
|
||||||
/// Genera una azione che poi verrà usata per l'entità associata.\
|
/// Genera una azione che poi verrà usata per l'entità associata.\
|
||||||
/// L'azione può essere generata in qualunque modo: casuale, sempre la stessa,
|
/// L'azione può essere generata in qualunque modo: casuale, sempre la stessa,
|
||||||
/// tramite interazione con console, o tramite una connessione ad un client.\
|
/// tramite interazione con console, o tramite una connessione ad un client.\
|
||||||
@@ -272,41 +276,13 @@ clone_trait_object!(Behavior);
|
|||||||
|
|
||||||
/// Semplice implementazione di un possibile comportamento di una entità.\
|
/// Semplice implementazione di un possibile comportamento di una entità.\
|
||||||
/// In questo caso l'entità resterà immobile nel punto in cui si trova per sempre.
|
/// In questo caso l'entità resterà immobile nel punto in cui si trova per sempre.
|
||||||
#[derive(Clone, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
pub struct Immovable;
|
pub struct Immovable;
|
||||||
#[typetag::serde]
|
#[typetag::serde]
|
||||||
impl Behavior for Immovable {
|
impl Behavior for Immovable {
|
||||||
fn update(&self, _floor: FloorView) {}
|
fn update(&self, _floor: FloorView) {}
|
||||||
|
fn you_died(&self, _floor: FloorView) {}
|
||||||
fn get_next_action(&self) -> Option<Action> {
|
fn get_next_action(&self) -> Option<Action> {
|
||||||
Some(Action::DoNothing)
|
Some(Action::DoNothing)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Semplice implementazione di una possibile interfaccia console.
|
|
||||||
#[derive(Clone, Serialize, Deserialize)]
|
|
||||||
pub struct ConsoleInput;
|
|
||||||
#[typetag::serde]
|
|
||||||
impl Behavior for ConsoleInput {
|
|
||||||
fn update(&self, floor: FloorView) {
|
|
||||||
let mut term = console::Term::stdout();
|
|
||||||
let _ = term.clear_screen();
|
|
||||||
let _ = term.write_fmt(format_args!("{}\n", floor));
|
|
||||||
}
|
|
||||||
fn get_next_action(&self) -> Option<Action> {
|
|
||||||
let mut term = console::Term::stdout();
|
|
||||||
let _ = term.write("Insert your action [wasd or enter for nothing]: ".as_bytes());
|
|
||||||
|
|
||||||
loop {
|
|
||||||
if let Ok(ch) = term.read_char() {
|
|
||||||
match ch {
|
|
||||||
'\n' => return Some(Action::DoNothing),
|
|
||||||
'w' => return Some(Action::Move(Direction::Up)),
|
|
||||||
'a' => return Some(Action::Move(Direction::Left)),
|
|
||||||
's' => return Some(Action::Move(Direction::Down)),
|
|
||||||
'd' => return Some(Action::Move(Direction::Right)),
|
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -4,12 +4,15 @@ use super::{
|
|||||||
};
|
};
|
||||||
use rand_pcg::Pcg32;
|
use rand_pcg::Pcg32;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::{collections::VecDeque, fmt::Display};
|
use std::{
|
||||||
|
collections::{HashMap, VecDeque},
|
||||||
|
fmt::Display,
|
||||||
|
};
|
||||||
|
|
||||||
/// Indica un piano del dungeon, in essa si possono trovare le celle in cui si
|
/// Indica un piano del dungeon, in essa si possono trovare le celle in cui si
|
||||||
/// cammina e le entità che abitano il piano.\
|
/// cammina e le entità che abitano il piano.\
|
||||||
/// Per poter accedere a questa struttura è necessario utilizzare FloorPtr e fare get()
|
/// Per poter accedere a questa struttura è necessario utilizzare FloorPtr e fare get()
|
||||||
#[derive(Clone, Deserialize, Serialize)]
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
pub struct Floor {
|
pub struct Floor {
|
||||||
level: usize,
|
level: usize,
|
||||||
grid: Vec<Vec<Cell>>,
|
grid: Vec<Vec<Cell>>,
|
||||||
@@ -43,6 +46,12 @@ impl Floor {
|
|||||||
self.players.iter().any(|player| player.is_alive())
|
self.players.iter().any(|player| player.is_alive())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Restituisce la grandezza di un lato del piano.\
|
||||||
|
/// Per avere la quantità di celle basterà prendere il valore ed elevarlo a 2.
|
||||||
|
pub fn get_size(&self) -> usize {
|
||||||
|
self.grid.len()
|
||||||
|
}
|
||||||
|
|
||||||
/// Restituisce il livello di profondità del piano
|
/// Restituisce il livello di profondità del piano
|
||||||
pub fn get_level(&self) -> usize {
|
pub fn get_level(&self) -> usize {
|
||||||
self.level
|
self.level
|
||||||
@@ -112,6 +121,21 @@ impl Floor {
|
|||||||
pub fn get_limited_view_floor<'a>(&'a self, entity: &'a Entity) -> FloorView<'a> {
|
pub fn get_limited_view_floor<'a>(&'a self, entity: &'a Entity) -> FloorView<'a> {
|
||||||
FloorView::new(self, entity)
|
FloorView::new(self, entity)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Ritorna un iteratore a tutte le entità del piano.\
|
||||||
|
/// Le entità del piano si dividono in giocatori e entità, e questo iteratore le ritorna tutte,
|
||||||
|
/// passando prima dai giocatori e poi da tutto il resto.
|
||||||
|
pub fn get_all_entities<'a>(&'a self) -> impl Iterator<Item = &Entity> + 'a {
|
||||||
|
self.players.iter().chain(self.entities.iter())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Controlla che nella posizione indicata non ci siano altre entità e restituisce il numero di collisioni trovate.\
|
||||||
|
/// Questo metodo controlla TUTTE le entità e i giocatori del piano, quindi si svolge in O(n)
|
||||||
|
fn collisions(&self, pos: &Position) -> usize {
|
||||||
|
self.get_all_entities()
|
||||||
|
.filter(|entity| entity.position == *pos)
|
||||||
|
.fold(0, |count, _| count + 1)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Struttura di mezzo tra un piano e il gioco vero e proprio.\
|
/// Struttura di mezzo tra un piano e il gioco vero e proprio.\
|
||||||
@@ -122,6 +146,13 @@ pub struct FloorView<'a> {
|
|||||||
pub floor: &'a Floor,
|
pub floor: &'a Floor,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// todo!() add docs
|
||||||
|
pub struct CellView<'a> {
|
||||||
|
pub position: Position,
|
||||||
|
pub entity: Option<&'a Entity>,
|
||||||
|
pub cell: &'a Cell,
|
||||||
|
}
|
||||||
|
|
||||||
impl<'a> FloorView<'a> {
|
impl<'a> FloorView<'a> {
|
||||||
/// Crea una vista del gioco corrente secondo la visione dell'entità passata in intput.\
|
/// 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
|
/// Il SimpleFloor risultante avrà il piano, entità, livello e giocatori che si trovano
|
||||||
@@ -133,17 +164,51 @@ impl<'a> FloorView<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Ritorna un iteratore contenente gli iteratori di ogni riga del piano.
|
||||||
|
pub fn get_grid(&self, view: usize) -> impl Iterator<Item = impl Iterator<Item = CellView>> {
|
||||||
|
let grid = &self.floor.grid;
|
||||||
|
let entities = self
|
||||||
|
.floor
|
||||||
|
.get_all_entities()
|
||||||
|
.chain(std::iter::once(self.entity))
|
||||||
|
.map(|entity| (&entity.position, entity))
|
||||||
|
.collect::<HashMap<_, _>>();
|
||||||
|
let entities = std::rc::Rc::new(std::cell::RefCell::new(entities));
|
||||||
|
|
||||||
|
let temp_x = self.entity.position.0.saturating_sub(view);
|
||||||
|
let temp_y = self.entity.position.1.saturating_sub(view);
|
||||||
|
let size_x = temp_x.saturating_add(2 * view).min(grid.len());
|
||||||
|
let size_y = temp_y.saturating_add(2 * view).min(grid.len());
|
||||||
|
let view_x = size_x.saturating_sub(2 * view);
|
||||||
|
let view_y = size_y.saturating_sub(2 * view);
|
||||||
|
|
||||||
|
(view_y..size_y).rev().map(move |y| {
|
||||||
|
let entities = entities.clone();
|
||||||
|
(view_x..size_x)
|
||||||
|
.map(move |x| Position(x, y))
|
||||||
|
.map(move |position| {
|
||||||
|
let cell = &grid[position.0][position.1];
|
||||||
|
let entity = entities.borrow_mut().remove(&position);
|
||||||
|
CellView {
|
||||||
|
position,
|
||||||
|
entity,
|
||||||
|
cell,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/// Rappresentazione del piano come matrice di char
|
/// Rappresentazione del piano come matrice di char
|
||||||
pub fn as_char_grid(&self) -> Vec<Vec<char>> {
|
pub fn as_char_grid(&self) -> Vec<Vec<char>> {
|
||||||
let grid = &self.floor.grid;
|
self.get_grid(self.floor.grid.len())
|
||||||
let size = grid.len();
|
.map(|iter| {
|
||||||
let mut grid = (0..size)
|
iter.flat_map(|view| {
|
||||||
.map(|y| {
|
if let Some(e) = view.entity {
|
||||||
(0..size)
|
return [' ', e.direction.as_char(), ' '];
|
||||||
.flat_map(|x| {
|
}
|
||||||
let cell = &grid[x][y];
|
|
||||||
let ch = cell.as_char();
|
let ch = view.cell.as_char();
|
||||||
match cell {
|
match view.cell {
|
||||||
Cell::Wall => [ch, ch, ch],
|
Cell::Wall => [ch, ch, ch],
|
||||||
_ => [' ', ch, ' '],
|
_ => [' ', ch, ' '],
|
||||||
}
|
}
|
||||||
@@ -151,11 +216,28 @@ impl<'a> FloorView<'a> {
|
|||||||
.chain(std::iter::once('\n'))
|
.chain(std::iter::once('\n'))
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>()
|
||||||
|
}
|
||||||
|
|
||||||
let pos = &self.entity.position;
|
/// todo!() add docs
|
||||||
grid[pos.1][pos.0 * 3 + 1] = self.entity.direction.as_char();
|
pub fn box_of(size: usize, iter: impl Iterator<Item = char>) -> impl Iterator<Item = char> {
|
||||||
grid
|
std::iter::once('╔')
|
||||||
|
.chain(std::iter::repeat('═').take(size + 2))
|
||||||
|
.chain(['╗', '\n'].into_iter())
|
||||||
|
.chain(iter.enumerate().flat_map(move |(i, c)| {
|
||||||
|
let modulo = i % size;
|
||||||
|
if modulo == 0 {
|
||||||
|
vec!['║', ' ', c]
|
||||||
|
} else if modulo == size - 1 {
|
||||||
|
vec![c, ' ', '║', '\n']
|
||||||
|
} else {
|
||||||
|
vec![c]
|
||||||
|
}
|
||||||
|
.into_iter()
|
||||||
|
}))
|
||||||
|
.chain(std::iter::once('╚'))
|
||||||
|
.chain(std::iter::repeat('═').take(size + 2))
|
||||||
|
.chain(['╝', '\n'].into_iter())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -164,7 +246,6 @@ impl<'a> Display for FloorView<'a> {
|
|||||||
let grid: String = self
|
let grid: String = self
|
||||||
.as_char_grid()
|
.as_char_grid()
|
||||||
.iter()
|
.iter()
|
||||||
.rev()
|
|
||||||
.map(|row| row.iter().collect::<String>())
|
.map(|row| row.iter().collect::<String>())
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ use std::{
|
|||||||
/// Rappresenta un Dungeon in stile RogueLike.\
|
/// Rappresenta un Dungeon in stile RogueLike.\
|
||||||
/// In esso possiamo trovare dei piani generati casualmente
|
/// In esso possiamo trovare dei piani generati casualmente
|
||||||
/// e dei giocatori che esplorano.
|
/// e dei giocatori che esplorano.
|
||||||
#[derive(Clone, Deserialize, Serialize)]
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
pub struct Dungeon {
|
pub struct Dungeon {
|
||||||
floors: Vec<Floor>,
|
floors: Vec<Floor>,
|
||||||
config: Config,
|
config: Config,
|
||||||
|
|||||||
@@ -54,13 +54,13 @@ impl<'a> Generator<'a> {
|
|||||||
/// Questo metodo creerà un piano avente delle stanze collegate tra di loro tramite dei
|
/// Questo metodo creerà un piano avente delle stanze collegate tra di loro tramite dei
|
||||||
/// corridoi; inoltre in esse verranno inseriti degli effetti.
|
/// corridoi; inoltre in esse verranno inseriti degli effetti.
|
||||||
pub fn build_floor(mut self) -> Floor {
|
pub fn build_floor(mut self) -> Floor {
|
||||||
let attempts = self.config.maze_generation.room_placing_attempts;
|
let maze_gen = &self.config.maze_generation;
|
||||||
let room_size = self.config.maze_generation.room_size.clone();
|
let room_size = self.config.maze_generation.room_size.clone();
|
||||||
let mut grid = MazeGenerator::new(self.size, room_size, &mut self.rng)
|
let mut grid = MazeGenerator::new(self.size, room_size, &mut self.rng)
|
||||||
.generate_rooms(attempts)
|
.generate_rooms(maze_gen.room_placing_attempts)
|
||||||
.generate_labyrinth(80)
|
.generate_labyrinth(maze_gen.straight_percentage)
|
||||||
.connect_regions()
|
.connect_regions()
|
||||||
.remove_dead_ends(0)
|
.remove_dead_ends(maze_gen.dead_ends)
|
||||||
.finalize(Cell::Wall, Cell::Empty);
|
.finalize(Cell::Wall, Cell::Empty);
|
||||||
|
|
||||||
self.rand_place(&mut grid, Cell::Entance);
|
self.rand_place(&mut grid, Cell::Entance);
|
||||||
@@ -183,7 +183,7 @@ impl<'a> MazeGenerator<'a> {
|
|||||||
/// Nel caso si può decidere di lasciare qualche zona che non va a collegarsi da nessuna parte
|
/// Nel caso si può decidere di lasciare qualche zona che non va a collegarsi da nessuna parte
|
||||||
/// mettendo un numero > 0 nel cutoff.\
|
/// mettendo un numero > 0 nel cutoff.\
|
||||||
/// Questo indicherà che nel labirinto ci saranno al massimo N corridioi senza uscita.
|
/// Questo indicherà che nel labirinto ci saranno al massimo N corridioi senza uscita.
|
||||||
pub fn remove_dead_ends(mut self, cutoff: usize) -> Self {
|
pub fn remove_dead_ends(mut self, cutoff: u32) -> Self {
|
||||||
let mut dead_ends = (0..self.size)
|
let mut dead_ends = (0..self.size)
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.flat_map(|x| {
|
.flat_map(|x| {
|
||||||
@@ -196,7 +196,7 @@ impl<'a> MazeGenerator<'a> {
|
|||||||
.collect::<VecDeque<_>>();
|
.collect::<VecDeque<_>>();
|
||||||
|
|
||||||
while let Some(pos) = dead_ends.pop_front() {
|
while let Some(pos) = dead_ends.pop_front() {
|
||||||
if dead_ends.len() < cutoff {
|
if dead_ends.len() < cutoff as usize {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -272,7 +272,7 @@ impl<'a> MazeGenerator<'a> {
|
|||||||
/// andare dritto quando crea il labirinto.\
|
/// andare dritto quando crea il labirinto.\
|
||||||
/// Con percentuali alte si avranno molti corridoi lunghi, con percentuali basse si avranno
|
/// Con percentuali alte si avranno molti corridoi lunghi, con percentuali basse si avranno
|
||||||
/// molte svolte.
|
/// molte svolte.
|
||||||
pub fn generate_labyrinth(mut self, mut straight_percentage: usize) -> Self {
|
pub fn generate_labyrinth(mut self, mut straight_percentage: u32) -> Self {
|
||||||
straight_percentage = straight_percentage.min(100); // cap at 100
|
straight_percentage = straight_percentage.min(100); // cap at 100
|
||||||
|
|
||||||
for x in (1..self.size).step_by(2) {
|
for x in (1..self.size).step_by(2) {
|
||||||
@@ -293,7 +293,7 @@ impl<'a> MazeGenerator<'a> {
|
|||||||
/// https://en.wikipedia.org/wiki/Maze_generation_algorithm#Iterative_implementation_(with_stack)\
|
/// https://en.wikipedia.org/wiki/Maze_generation_algorithm#Iterative_implementation_(with_stack)\
|
||||||
/// Il parametro straight_percentage indica quanto "scava" i corridoi del labirinto
|
/// Il parametro straight_percentage indica quanto "scava" i corridoi del labirinto
|
||||||
/// senza girare, e quindi creando lunghi segmenti.
|
/// senza girare, e quindi creando lunghi segmenti.
|
||||||
fn grow_maze(&mut self, start: Position, straight_percentage: usize) {
|
fn grow_maze(&mut self, start: Position, straight_percentage: u32) {
|
||||||
self.current_region += 1;
|
self.current_region += 1;
|
||||||
self.set(&start, Some(self.current_region));
|
self.set(&start, Some(self.current_region));
|
||||||
|
|
||||||
@@ -401,7 +401,7 @@ impl<'a> MazeGenerator<'a> {
|
|||||||
/// mentre i lati non hanno dimensione.\
|
/// mentre i lati non hanno dimensione.\
|
||||||
/// I punti quindi salvati sono il minimo e il massimo di un rettangolo ed indicano il
|
/// I punti quindi salvati sono il minimo e il massimo di un rettangolo ed indicano il
|
||||||
/// punto più in basso da dove inizia l'area e quello più in alto.
|
/// punto più in basso da dove inizia l'area e quello più in alto.
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
struct Room {
|
struct Room {
|
||||||
lo: Position,
|
lo: Position,
|
||||||
hi: Position,
|
hi: Position,
|
||||||
|
|||||||
121
src/es03_game/mod.rs
Normal file
121
src/es03_game/mod.rs
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
use self::{
|
||||||
|
cell::Cell,
|
||||||
|
config::Config,
|
||||||
|
entities::{Action, Behavior, Direction},
|
||||||
|
floor::FloorView,
|
||||||
|
game::Dungeon,
|
||||||
|
};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::io::Write;
|
||||||
|
|
||||||
|
pub mod cell;
|
||||||
|
pub mod config;
|
||||||
|
pub mod entities;
|
||||||
|
pub mod floor;
|
||||||
|
pub mod game;
|
||||||
|
pub mod generator;
|
||||||
|
|
||||||
|
/** Es.3
|
||||||
|
* Implementare una libreria che permetta di realizzare il seguente gioco.
|
||||||
|
* Il Campo di gioco e' una matrice n x n di celle le celle sui 4 lati sono dei muri e all'interno le celle possono essere
|
||||||
|
* - vuote
|
||||||
|
* - contenere cibo (un intero positivo)
|
||||||
|
* - contenere un veleno (un intero positivo)
|
||||||
|
*
|
||||||
|
* Un Giocatore si muove in questa matrice iniziando da una posizione casuale. Il giocatore ha
|
||||||
|
* - Direzione in cui si muove: Su, Giu', Destra, Sinistra
|
||||||
|
* - Posizione nella matrice
|
||||||
|
* - una forza (un intero positivo)
|
||||||
|
*
|
||||||
|
* Quando si muove avanza di una posizione nella direzione in cui il giocatore si muove. Una Configurazione e'
|
||||||
|
* un campo di gioco, e un giocatore in una posizione del campo per questa struttura implementate il trait Display
|
||||||
|
*
|
||||||
|
* Il gioco inizia con una configurazione in cui nella matrice ci sono m caselle con cibo e m con veleno (in posizioni casuali), un giocatore in una cella libera e un numero massimo di mosse.
|
||||||
|
* Ad ogni iterazione: Si lancia una moneta (Testa o Croce) se
|
||||||
|
* - Testa il giocatore si muove di una posizione nella direzione in cui si sta muovendo
|
||||||
|
* - altrimenti sceglie casualmente una dell 4 direzioni e fa un passo in quella direzione.
|
||||||
|
*
|
||||||
|
* Se la cella in cui si finisce
|
||||||
|
* contiene cibo, si aggiunge la quantita' di cibo alla forza
|
||||||
|
* contiene veleno, si decrementa la quantita' di veleno dalla forza
|
||||||
|
* e' un muro il giocatore rimbalza, cioe' resta nella stessa posizione ma cambia la sua direzione nella direzione opposta.
|
||||||
|
*
|
||||||
|
* Il gioco finisce quando
|
||||||
|
* - il giocatore finisce la forza (cioe' questa diventerebbe un valore <=0) e in questo caso PERDE
|
||||||
|
* - raggiunge il numero massimo di mosse nel qual caso VINCE
|
||||||
|
*
|
||||||
|
* Per n, m, le quantità iniziali dei vari elementi (elemento, cibo, forza) e il numero massimo di mosse usate variabili che possano essere inserite dall'utente.
|
||||||
|
* Se volete potete anche cambiare le regole del gioco.
|
||||||
|
* Mettere main e definizioni in files separati (le definizioni in uno o più files) e scrivete i test in una directory a parte.
|
||||||
|
*/
|
||||||
|
pub fn run_console(player: String) {
|
||||||
|
let mut config = Config::default();
|
||||||
|
config.game_seed = rand::random();
|
||||||
|
|
||||||
|
let mut game = Dungeon::new_with(config);
|
||||||
|
game.add_player(player, Box::new(ConsoleInput));
|
||||||
|
|
||||||
|
while game.has_players() {
|
||||||
|
game.compute_turn();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Implementazione di una possibile interfaccia console.
|
||||||
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
|
pub struct ConsoleInput;
|
||||||
|
impl ConsoleInput {
|
||||||
|
fn floor_as_string(floor: &FloorView) -> String {
|
||||||
|
let view = 5;
|
||||||
|
let size = (2 * view) * 3;
|
||||||
|
let iter = floor.get_grid(view).flat_map(|iter| {
|
||||||
|
iter.flat_map(|view| {
|
||||||
|
if let Some(e) = view.entity {
|
||||||
|
return [' ', e.direction.as_char(), ' '].into_iter();
|
||||||
|
}
|
||||||
|
|
||||||
|
let ch = view.cell.as_char();
|
||||||
|
match view.cell {
|
||||||
|
Cell::Wall => [ch, ch, ch],
|
||||||
|
_ => [' ', ch, ' '],
|
||||||
|
}
|
||||||
|
.into_iter()
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
FloorView::box_of(size, iter).collect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[typetag::serde]
|
||||||
|
impl Behavior for ConsoleInput {
|
||||||
|
fn update(&self, floor: FloorView) {
|
||||||
|
let mut term = console::Term::stdout();
|
||||||
|
let _ = term.clear_screen();
|
||||||
|
let _ = term.write_fmt(format_args!(
|
||||||
|
"{}{}\n",
|
||||||
|
Self::floor_as_string(&floor),
|
||||||
|
floor.entity
|
||||||
|
));
|
||||||
|
}
|
||||||
|
fn you_died(&self, floor: FloorView) {
|
||||||
|
let mut term = console::Term::stdout();
|
||||||
|
let _ = term.clear_screen();
|
||||||
|
let _ = term.write_fmt(format_args!("{}\nYOU DIED!\n", floor));
|
||||||
|
}
|
||||||
|
fn get_next_action(&self) -> Option<Action> {
|
||||||
|
let mut term = console::Term::stdout();
|
||||||
|
let _ = term.write("Insert your action [wasd or space for nothing]: ".as_bytes());
|
||||||
|
|
||||||
|
loop {
|
||||||
|
if let Ok(ch) = term.read_char() {
|
||||||
|
match ch {
|
||||||
|
' ' => return Some(Action::DoNothing),
|
||||||
|
'w' => return Some(Action::Move(Direction::Up)),
|
||||||
|
'a' => return Some(Action::Move(Direction::Left)),
|
||||||
|
's' => return Some(Action::Move(Direction::Down)),
|
||||||
|
'd' => return Some(Action::Move(Direction::Right)),
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user