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.
|
||||
/// Nel caso in cui passi sopra una entià esiste un metodo entity_over che
|
||||
/// gestisce le varie casistiche.
|
||||
#[derive(Clone, Deserialize, Serialize)]
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
pub enum Cell {
|
||||
Entance,
|
||||
Exit,
|
||||
@@ -71,7 +71,7 @@ impl Display for Cell {
|
||||
/// 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 {
|
||||
pub trait Effect: DynClone + core::fmt::Debug {
|
||||
/// 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
|
||||
@@ -89,7 +89,7 @@ clone_trait_object!(Effect);
|
||||
/// 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)]
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct InstantDamage(pub i32);
|
||||
#[typetag::serde]
|
||||
impl Effect for InstantDamage {
|
||||
@@ -105,7 +105,7 @@ impl Effect for InstantDamage {
|
||||
/// 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)]
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct Confusion(pub u8);
|
||||
#[typetag::serde]
|
||||
impl Effect for Confusion {
|
||||
@@ -128,7 +128,7 @@ impl Effect for Confusion {
|
||||
/// 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)]
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct TurnBasedDamage {
|
||||
time: u8,
|
||||
damage: i32,
|
||||
|
||||
@@ -8,7 +8,7 @@ use std::ops::Range;
|
||||
/// 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 standard.
|
||||
#[derive(Clone, Deserialize, Serialize)]
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
pub struct Config {
|
||||
pub game_seed: u64,
|
||||
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
|
||||
/// rimanga dritto o viri.\
|
||||
/// *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 floor_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\
|
||||
/// Se in Config mettiamo 15 effetti per piano, allora avremo
|
||||
/// in media 10 A e 5 B per ogni piano.
|
||||
#[derive(Clone, Deserialize, Serialize)]
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
pub struct ConfigEffect {
|
||||
pub floors: Range<usize>,
|
||||
pub effect: Box<dyn Effect>,
|
||||
pub priority: usize,
|
||||
pub priority: u32,
|
||||
}
|
||||
|
||||
/// Valori di base per le statistiche di un giocatore.\
|
||||
/// Esse verranno utilizzate quando un giocatore verrà creato.
|
||||
#[derive(Clone, Deserialize, Serialize)]
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
pub struct ConfigPlayer {
|
||||
pub health: i32,
|
||||
pub attack: i32,
|
||||
@@ -61,14 +61,14 @@ pub struct ConfigPlayer {
|
||||
/// 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)]
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
pub struct ConfigEntity {
|
||||
pub floors: Range<usize>,
|
||||
pub name: String,
|
||||
pub decider: Box<dyn Behavior>,
|
||||
pub health: i32,
|
||||
pub attack: i32,
|
||||
pub priority: usize,
|
||||
pub priority: u32,
|
||||
}
|
||||
|
||||
impl Default for Config {
|
||||
@@ -103,8 +103,8 @@ impl Default for Config {
|
||||
entities: vec![],
|
||||
entities_total: 0,
|
||||
player_stats: ConfigPlayer {
|
||||
health: 1000,
|
||||
attack: 100,
|
||||
health: 100,
|
||||
attack: 10,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ use dyn_clone::{clone_trait_object, DynClone};
|
||||
use rand::Rng;
|
||||
use rand_pcg::Pcg32;
|
||||
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.\
|
||||
/// È 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.\
|
||||
/// È 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 {
|
||||
Up,
|
||||
Down,
|
||||
@@ -85,7 +85,7 @@ impl Display for Direction {
|
||||
}
|
||||
|
||||
/// Rappresenta una entità all'interno del dungeon.
|
||||
#[derive(Clone, Deserialize, Serialize)]
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
pub struct Entity {
|
||||
name: String,
|
||||
effects: VecDeque<Box<dyn Effect>>,
|
||||
@@ -210,7 +210,7 @@ impl Display for Entity {
|
||||
|
||||
/// 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)]
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
pub enum Action {
|
||||
Move(Direction),
|
||||
//Attack(Direction),
|
||||
@@ -253,12 +253,16 @@ impl Action {
|
||||
/// 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 Behavior: DynClone {
|
||||
pub trait Behavior: DynClone + core::fmt::Debug {
|
||||
/// In questo metodo viene passata una struttura che contiene una rappresentazione del
|
||||
/// piano semplice, avente solo delle informazioni parziali.\
|
||||
/// Questo serve a mostrare eventualmente delle possibili informazioni all'utente
|
||||
/// o di registrare dei valori per l'algoritmo di generazione delle azioni.
|
||||
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.\
|
||||
/// L'azione può essere generata in qualunque modo: casuale, sempre la stessa,
|
||||
/// 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à.\
|
||||
/// 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;
|
||||
#[typetag::serde]
|
||||
impl Behavior for Immovable {
|
||||
fn update(&self, _floor: FloorView) {}
|
||||
fn you_died(&self, _floor: FloorView) {}
|
||||
fn get_next_action(&self) -> Option<Action> {
|
||||
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 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
|
||||
/// cammina e le entità che abitano il piano.\
|
||||
/// Per poter accedere a questa struttura è necessario utilizzare FloorPtr e fare get()
|
||||
#[derive(Clone, Deserialize, Serialize)]
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
pub struct Floor {
|
||||
level: usize,
|
||||
grid: Vec<Vec<Cell>>,
|
||||
@@ -43,6 +46,12 @@ impl Floor {
|
||||
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
|
||||
pub fn get_level(&self) -> usize {
|
||||
self.level
|
||||
@@ -112,6 +121,21 @@ impl Floor {
|
||||
pub fn get_limited_view_floor<'a>(&'a self, entity: &'a Entity) -> FloorView<'a> {
|
||||
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.\
|
||||
@@ -122,6 +146,13 @@ pub struct FloorView<'a> {
|
||||
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> {
|
||||
/// 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
|
||||
@@ -133,29 +164,80 @@ 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
|
||||
pub fn as_char_grid(&self) -> Vec<Vec<char>> {
|
||||
let grid = &self.floor.grid;
|
||||
let size = grid.len();
|
||||
let mut grid = (0..size)
|
||||
.map(|y| {
|
||||
(0..size)
|
||||
.flat_map(|x| {
|
||||
let cell = &grid[x][y];
|
||||
let ch = cell.as_char();
|
||||
match cell {
|
||||
Cell::Wall => [ch, ch, ch],
|
||||
_ => [' ', ch, ' '],
|
||||
}
|
||||
})
|
||||
.chain(std::iter::once('\n'))
|
||||
.collect::<Vec<_>>()
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
self.get_grid(self.floor.grid.len())
|
||||
.map(|iter| {
|
||||
iter.flat_map(|view| {
|
||||
if let Some(e) = view.entity {
|
||||
return [' ', e.direction.as_char(), ' '];
|
||||
}
|
||||
|
||||
let pos = &self.entity.position;
|
||||
grid[pos.1][pos.0 * 3 + 1] = self.entity.direction.as_char();
|
||||
grid
|
||||
let ch = view.cell.as_char();
|
||||
match view.cell {
|
||||
Cell::Wall => [ch, ch, ch],
|
||||
_ => [' ', ch, ' '],
|
||||
}
|
||||
})
|
||||
.chain(std::iter::once('\n'))
|
||||
.collect::<Vec<_>>()
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
}
|
||||
|
||||
/// todo!() add docs
|
||||
pub fn box_of(size: usize, iter: impl Iterator<Item = char>) -> impl Iterator<Item = char> {
|
||||
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
|
||||
.as_char_grid()
|
||||
.iter()
|
||||
.rev()
|
||||
.map(|row| row.iter().collect::<String>())
|
||||
.collect();
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ use std::{
|
||||
/// Rappresenta un Dungeon in stile RogueLike.\
|
||||
/// In esso possiamo trovare dei piani generati casualmente
|
||||
/// e dei giocatori che esplorano.
|
||||
#[derive(Clone, Deserialize, Serialize)]
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
pub struct Dungeon {
|
||||
floors: Vec<Floor>,
|
||||
config: Config,
|
||||
|
||||
@@ -54,13 +54,13 @@ impl<'a> Generator<'a> {
|
||||
/// Questo metodo creerà un piano avente delle stanze collegate tra di loro tramite dei
|
||||
/// corridoi; inoltre in esse verranno inseriti degli effetti.
|
||||
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 mut grid = MazeGenerator::new(self.size, room_size, &mut self.rng)
|
||||
.generate_rooms(attempts)
|
||||
.generate_labyrinth(80)
|
||||
.generate_rooms(maze_gen.room_placing_attempts)
|
||||
.generate_labyrinth(maze_gen.straight_percentage)
|
||||
.connect_regions()
|
||||
.remove_dead_ends(0)
|
||||
.remove_dead_ends(maze_gen.dead_ends)
|
||||
.finalize(Cell::Wall, Cell::Empty);
|
||||
|
||||
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
|
||||
/// mettendo un numero > 0 nel cutoff.\
|
||||
/// 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)
|
||||
.into_iter()
|
||||
.flat_map(|x| {
|
||||
@@ -196,7 +196,7 @@ impl<'a> MazeGenerator<'a> {
|
||||
.collect::<VecDeque<_>>();
|
||||
|
||||
while let Some(pos) = dead_ends.pop_front() {
|
||||
if dead_ends.len() < cutoff {
|
||||
if dead_ends.len() < cutoff as usize {
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -272,7 +272,7 @@ impl<'a> MazeGenerator<'a> {
|
||||
/// andare dritto quando crea il labirinto.\
|
||||
/// Con percentuali alte si avranno molti corridoi lunghi, con percentuali basse si avranno
|
||||
/// 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
|
||||
|
||||
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)\
|
||||
/// Il parametro straight_percentage indica quanto "scava" i corridoi del labirinto
|
||||
/// 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.set(&start, Some(self.current_region));
|
||||
|
||||
@@ -401,7 +401,7 @@ impl<'a> MazeGenerator<'a> {
|
||||
/// mentre i lati non hanno dimensione.\
|
||||
/// 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.
|
||||
#[derive(Clone, Copy)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
struct Room {
|
||||
lo: 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