- priority in config now works
- changed behavior with mut self
-  modified box for the console
This commit is contained in:
2024-05-24 11:37:45 +02:00
parent 59e840cc25
commit 24d004db88
6 changed files with 179 additions and 89 deletions

View File

@@ -1,6 +1,6 @@
use super::{ use super::{
cell::{Confusion, Effect, InstantDamage}, cell::{Confusion, Effect, InstantDamage},
entities::Behavior, entities::{Behavior, RandomMovement},
}; };
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::ops::Range; use std::ops::Range;
@@ -65,7 +65,7 @@ pub struct ConfigPlayer {
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 behavior: Box<dyn Behavior>,
pub health: i32, pub health: i32,
pub attack: i32, pub attack: i32,
pub priority: u32, pub priority: u32,
@@ -96,12 +96,19 @@ impl Default for Config {
ConfigEffect { ConfigEffect {
effect: Box::new(Confusion(10)), effect: Box::new(Confusion(10)),
floors: 0..255, floors: 0..255,
priority: 1, priority: 10,
}, },
], ],
effects_total: 45, effects_total: 45,
entities: vec![], entities: vec![ConfigEntity {
entities_total: 0, floors: 0..255,
name: "Basic enemy".to_string(),
behavior: Box::new(RandomMovement::new()),
health: 30,
attack: 10,
priority: 1,
}],
entities_total: 10,
player_stats: ConfigPlayer { player_stats: ConfigPlayer {
health: 100, health: 100,
attack: 10, attack: 10,

View File

@@ -1,9 +1,9 @@
use super::{ use super::{
cell::Effect, cell::{Cell, Effect},
floor::{Floor, FloorView}, floor::{Floor, FloorView},
}; };
use dyn_clone::{clone_trait_object, DynClone}; use dyn_clone::{clone_trait_object, DynClone};
use rand::Rng; use rand::{Rng, SeedableRng};
use rand_pcg::Pcg32; use rand_pcg::Pcg32;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::{collections::VecDeque, fmt::Display, mem}; use std::{collections::VecDeque, fmt::Display, mem};
@@ -17,12 +17,13 @@ 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, Debug, Deserialize, Serialize)] #[derive(PartialEq, Eq, Hash, Clone, Copy, Default, Debug, Deserialize, Serialize)]
pub enum Direction { pub enum Direction {
Up, Up,
Down, Down,
Left, Left,
Right, Right,
#[default]
None, None,
} }
@@ -89,7 +90,7 @@ impl Display for Direction {
pub struct Entity { pub struct Entity {
name: String, name: String,
effects: VecDeque<Box<dyn Effect>>, effects: VecDeque<Box<dyn Effect>>,
behavior: Box<dyn Behavior>, behavior: Option<Box<dyn Behavior>>,
pub buffer: Action, pub buffer: Action,
pub position: Position, pub position: Position,
pub direction: Direction, pub direction: Direction,
@@ -106,7 +107,7 @@ impl Entity {
pub fn new(name: String, health: i32, attack: i32, behavior: Box<dyn Behavior>) -> Self { pub fn new(name: String, health: i32, attack: i32, behavior: Box<dyn Behavior>) -> Self {
Self { Self {
name, name,
behavior, behavior: Some(behavior),
position: Position(0, 0), position: Position(0, 0),
attack, attack,
health, health,
@@ -164,33 +165,41 @@ impl Entity {
/// Permette all'entità di mostrare il piano in cui si trova e di fare una mossa.\ /// Permette all'entità di mostrare il piano in cui si trova e di fare una mossa.\
/// Il piano viene mostrato tramite il behavior dell'entità e successivamente viene chiesto di fare un'azione.\ /// Il piano viene mostrato tramite il behavior dell'entità e successivamente viene chiesto di fare un'azione.\
/// Dopodichè vengono calcolati tutti gli effetti che devono essere applicati all'entità.\ /// Dopodichè vengono calcolati tutti gli effetti che devono essere applicati all'entità.\
/// Nel caso in cui l'entità non sia più in vita questo metodo ritornerà false /// Nel caso in cui l'entità non sia più in vita questo metodo ritornerà None
/// e non permetterà all'entità di fare update.\ /// e l' entità smetterà di esistere.\
/// Nel caso in cui l'entità non riesca a fare l'update viene ritornato false.\ /// Nel caso in cui l'entità non riesca a fare l'update viene ritornato None.\
/// Cio significa che l'entità verrà rimossa dal gioco. /// Cio significa che l'entità verrà rimossa dal gioco.
pub fn update(&mut self, floor: &mut Floor) -> bool { pub fn update(mut self, floor: &mut Floor) -> Option<Self> {
let mut behavior = mem::take(&mut self.behavior).unwrap();
if !self.is_alive() { if !self.is_alive() {
self.behavior.you_died(floor.get_limited_view_floor(self)); return self.die(behavior, floor);
return false;
} }
self.behavior.update(floor.get_limited_view_floor(self)); behavior.update(floor.get_limited_view_floor(&self));
let action = self.compute_action(floor); let action = self.compute_action(&mut behavior, floor);
if action.is_none() { if action.is_none() {
return false; return None;
} }
if !self.is_alive() { if !self.is_alive() {
self.behavior.you_died(floor.get_limited_view_floor(self)); return self.die(behavior, floor);
return false;
} }
self.compute_effects(floor); self.compute_effects(floor);
if !self.is_alive() { if !self.is_alive() {
self.behavior.you_died(floor.get_limited_view_floor(self)); return self.die(behavior, floor);
return false
} }
true
self.behavior = Some(behavior);
Some(self)
}
/// metodo usato per la rimozione dell' entità e del suo behavior
fn die(self, mut behavior: Box<dyn Behavior>, floor: &Floor) -> Option<Self> {
let view = floor.get_limited_view_floor(&self);
behavior.on_death(view);
None
} }
/// calcola gli effetti e li applica all'entità. /// calcola gli effetti e li applica all'entità.
@@ -204,8 +213,8 @@ impl Entity {
} }
/// prende una decisione e applica l'azione da fare /// prende una decisione e applica l'azione da fare
/// L'azione compiuta viene restituita, altrimenti None /// L'azione compiuta viene restituita, altrimenti None
fn compute_action(&mut self, floor: &mut Floor) -> Option<Action> { fn compute_action(&mut self, behavior: &mut Box<dyn Behavior>, floor: &mut Floor) -> Option<Action> {
let action = self.behavior.get_next_action()?; let action = behavior.get_next_action()?;
let action = match self.buffer { let action = match self.buffer {
Action::DoNothing => action, Action::DoNothing => action,
_ => mem::replace(&mut self.buffer, Action::DoNothing), _ => mem::replace(&mut self.buffer, Action::DoNothing),
@@ -236,10 +245,11 @@ 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, Debug, Deserialize, Serialize)] #[derive(Clone, Default, Debug, Deserialize, Serialize)]
pub enum Action { pub enum Action {
Move(Direction), Move(Direction),
//Attack(Direction), //Attack(Direction),
#[default]
DoNothing, DoNothing,
} }
@@ -257,7 +267,7 @@ impl Action {
direction.move_from(&mut entity.position); direction.move_from(&mut entity.position);
entity.direction = direction; entity.direction = direction;
let cell = floor.get_cell(&entity.position); let cell = floor.get_cell_mut(&entity.position);
cell.entity_over(entity); cell.entity_over(entity);
} }
} }
@@ -283,12 +293,14 @@ 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); /// Non è necessario implementarla.
fn update(&mut self, _view: FloorView) {}
/// Funzione che viene richiamata quando l'entità muore.\ /// Funzione che viene richiamata quando l'entità muore.\
/// I parametri servono a far vedere un'ultima volta i dati del piano corrente all'entità /// I parametri servono a far vedere un'ultima volta i dati del piano corrente all'entità
/// in modo che possa eventualmente fare ulteriori calcoli. /// in modo che possa eventualmente fare ulteriori calcoli.\
fn you_died(&self, floor: FloorView); /// Non è necessario implementarla.
fn on_death(&mut self, _view: 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.\
@@ -296,7 +308,7 @@ pub trait Behavior: DynClone + core::fmt::Debug {
/// Nel caso in cui venga restituito None come valore, l'entità verrà rimossa dal gioco.\ /// 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, /// 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. /// ma anche una possibilità che alcune entità rare possano sparire.
fn get_next_action(&self) -> Option<Action>; fn get_next_action(&mut self) -> Option<Action>;
} }
clone_trait_object!(Behavior); clone_trait_object!(Behavior);
@@ -306,9 +318,37 @@ clone_trait_object!(Behavior);
pub struct Immovable; pub struct Immovable;
#[typetag::serde] #[typetag::serde]
impl Behavior for Immovable { impl Behavior for Immovable {
fn update(&self, _floor: FloorView) {} fn get_next_action(&mut self) -> Option<Action> {
fn you_died(&self, _floor: FloorView) {}
fn get_next_action(&self) -> Option<Action> {
Some(Action::DoNothing) Some(Action::DoNothing)
} }
} }
/// Semplice implementazione di un possibile comportamento di una entità.\
/// In questo caso l'entità si mouverà in maniera casuale evitando le caselle speciali.
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct RandomMovement {
action: Action,
rng: Pcg32,
}
impl RandomMovement {
pub fn new() -> Self {
Self {
action: Action::DoNothing,
rng: Pcg32::seed_from_u64(0),
}
}
}
#[typetag::serde]
impl Behavior for RandomMovement {
fn update(&mut self, view: FloorView) {
let dir = Direction::random(&mut self.rng);
let mut pos = view.entity.position.clone();
dir.move_from(&mut pos);
if let Cell::Empty = view.floor.get_cell(&pos) {
self.action = Action::Move(dir);
}
}
fn get_next_action(&mut self) -> Option<Action> {
Some(mem::take(&mut self.action))
}
}

View File

@@ -68,13 +68,25 @@ impl Floor {
/// Nel caso in cui la posizione non sia all'interno del piano, essa viene modificata /// Nel caso in cui la posizione non sia all'interno del piano, essa viene modificata
/// facendola rientrare nei limiti di esso.\ /// facendola rientrare nei limiti di esso.\
/// Es. pos(2,3) ma il piano è di max 2 allora diventa -> pos(2,2) /// Es. pos(2,3) ma il piano è di max 2 allora diventa -> pos(2,2)
pub fn get_cell(&mut self, pos: &Position) -> &mut Cell { pub fn get_cell_mut(&mut self, pos: &Position) -> &mut Cell {
let len = self.grid.len() - 1; let len = self.grid.len() - 1;
let x = pos.0.min(len); let x = pos.0.min(len);
let y = pos.1.min(len); let y = pos.1.min(len);
&mut self.grid[x][y] &mut self.grid[x][y]
} }
/// Restituisce la cella nella posizione indicata.\
/// Con essa si può leggere la cella senza però la possibilità di modificarla.\
/// Nel caso in cui la posizione non sia all'interno del piano, essa viene modificata
/// facendola rientrare nei limiti di esso.\
/// Es. pos(2,3) ma il piano è di max 2 allora diventa -> pos(2,2)
pub fn get_cell(&self, pos: &Position) -> &Cell {
let len = self.grid.len() - 1;
let x = pos.0.min(len);
let y = pos.1.min(len);
&self.grid[x][y]
}
/// Restituisce la posizione dell'entrata del piano.\ /// Restituisce la posizione dell'entrata del piano.\
/// Utile come spawn per quando i giocatori arrivano al piano. /// Utile come spawn per quando i giocatori arrivano al piano.
pub fn get_entrance(&mut self) -> Position { pub fn get_entrance(&mut self) -> Position {
@@ -93,20 +105,6 @@ impl Floor {
.expect("Entrance of the floor should be inside the grid!") .expect("Entrance of the floor should be inside the grid!")
} }
/// Fa l'update di tutti i giocatori e rimuove eventualmente quelli non più in vita, restituendoli dentro un vec
pub fn update_players(&mut self) -> Vec<Entity> {
let mut next_floor = vec![];
for _ in 0..self.players.len() {
let mut player = self.players.pop_front().unwrap();
if player.update(self) {
self.players.push_back(player);
} else {
next_floor.push(player);
}
}
next_floor
}
/// Ritorna un eventuale giocatore che si trova sopra la cella di uscita del piano.\ /// Ritorna un eventuale giocatore che si trova sopra la cella di uscita del piano.\
/// Nel caso in cui non ci siano giocatori sopra, questo metodo ritornerà None. /// Nel caso in cui non ci siano giocatori sopra, questo metodo ritornerà None.
pub fn get_player_at_exit(&mut self) -> Option<Entity> { pub fn get_player_at_exit(&mut self) -> Option<Entity> {
@@ -130,11 +128,21 @@ impl Floor {
} }
} }
/// Fa l'update di tutti i giocatori e rimuove quelli non più in vita
pub fn update_players(&mut self) {
for _ in 0..self.players.len() {
let player = self.players.pop_front().unwrap();
if let Some(player) = player.update(self) {
self.players.push_back(player);
}
}
}
/// Fa l'update di tutte le entità e rimuove eventualmente quelle non più in vita /// Fa l'update di tutte le entità e rimuove eventualmente quelle non più in vita
pub fn update_entities(&mut self) { pub fn update_entities(&mut self) {
for _ in 0..self.entities.len() { for _ in 0..self.entities.len() {
let mut entity = self.entities.pop_front().unwrap(); let entity = self.entities.pop_front().unwrap();
if entity.update(self) { if let Some(entity) = entity.update(self) {
self.entities.push_back(entity); self.entities.push_back(entity);
} }
} }

View File

@@ -23,7 +23,7 @@ pub struct Dungeon {
} }
impl Dungeon { impl Dungeon {
/// Crea una nuova istanza di un dungeon con le configurazioni i default /// Crea una nuova istanza di un dungeon con le configurazioni di default
pub fn new() -> Self { pub fn new() -> Self {
Self::new_with(Config::default()) Self::new_with(Config::default())
} }

View File

@@ -74,16 +74,20 @@ impl<'a> Generator<'a> {
self.rand_place_effects(&mut grid); self.rand_place_effects(&mut grid);
Floor::new(self.level, self.rng, vec![], grid) Floor::new(self.level, self.rng, vec![], grid)
} }
/// todo!() docs
fn rand_place_entities(&mut self, grid: &mut Vec<Vec<Cell>>) {
todo!()
}
/// piazza gli effetti della confgurazione in modo casuale su tutto il piano.\ /// piazza gli effetti della confgurazione in modo casuale su tutto il piano.\
/// essi vengono piazzati solamente sulle celle Empty /// essi vengono piazzati solamente sulle celle Empty
fn rand_place_effects(&mut self, grid: &mut Vec<Vec<Cell>>) { fn rand_place_effects(&mut self, grid: &mut Vec<Vec<Cell>>) {
let total = self.config.effects_total; let effects = vec_filter(&self.config.effects, |e| {
let original = &self.config.effects; e.floors.contains(&self.level).then(|| (e.priority, e))
let effects = Self::vec_filter(original, |val| val.floors.contains(&self.level)); });
for _ in 0..total { for _ in 0..self.config.effects_total {
let index = self.rng.gen_range(0..effects.len()); let effect = vec_get_sample(&effects, &mut self.rng).effect.clone();
let effect = effects[index].effect.clone();
let cell = Cell::Special(effect); let cell = Cell::Special(effect);
self.rand_place(grid, cell, 0..self.size, 0..self.size); self.rand_place(grid, cell, 0..self.size, 0..self.size);
} }
@@ -106,14 +110,29 @@ impl<'a> Generator<'a> {
} }
} }
} }
/// 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 /// crea una vista del vettore passato in input dopo aver applicato la funzione di filtro
.clone() pub fn vec_filter<T, F>(original: &Vec<T>, filter: F) -> Vec<(f32, &T)>
.into_iter() where
.filter_map(|val| filter(&val).then(|| val)) F: FnMut(&T) -> Option<(u32, &T)>,
.collect() {
} let temp = original.iter().filter_map(filter).collect::<Vec<_>>();
let max = temp.iter().fold(0, |a, b| a.max(b.0)) + 1;
let total = temp.iter().map(|(p, _)| (max - *p) as f32).sum::<f32>();
let mut accum = 0.0;
temp.into_iter()
.map(|(p, item)| {
accum += (max - p) as f32 / total;
(accum, item)
})
.collect()
}
/// todo!() docs
pub fn vec_get_sample<'a, T>(vec: &Vec<(f32, &'a T)>, rng: &mut Pcg32) -> &'a T {
let sample = rng.gen_range(0.0..1.0);
vec.iter().filter(|(p, _)| *p >= sample).next().unwrap().1
} }
/// Utile per la generazione del labirinto.\ /// Utile per la generazione del labirinto.\

View File

@@ -56,10 +56,41 @@ pub fn run_console(player: String, seed: u64) {
game.add_player(player, Box::new(ConsoleInput)); game.add_player(player, Box::new(ConsoleInput));
while game.has_players() { while game.has_players() {
let _ = game.save("save.json");
game.compute_turn(); game.compute_turn();
} }
} }
/// todo!() add docs
pub fn box_of(
size: usize,
title: String,
iter: impl Iterator<Item = String>,
) -> impl Iterator<Item = String> {
assert!(
size >= title.len(),
"Title must not exceed the size of the box!"
);
let len = (size - title.len()) / 2;
let correction = if 2 * len + title.len() < size { 1 } else { 0 };
std::iter::once("".to_string())
.chain(std::iter::repeat("".to_string()).take(len + 1))
.chain(std::iter::once(title))
.chain(std::iter::repeat("".to_string()).take(len + 1 + correction))
.chain(std::iter::once("\n".to_string()))
.chain(iter.map(|string| {
std::iter::once("".to_string())
.chain(std::iter::once(string))
.chain(std::iter::once("\n".to_string()))
.collect()
}))
.chain(std::iter::once("".to_string()))
.chain(std::iter::repeat("".to_string()).take(size + 2))
.chain(std::iter::once("\n".to_string()))
}
const COLOR_RESET: &str = "\x1b[0m"; const COLOR_RESET: &str = "\x1b[0m";
const COLOR_EFFECT: &str = "\x1b[95m"; const COLOR_EFFECT: &str = "\x1b[95m";
const COLOR_ENEMY: &str = "\x1b[38;5;1m"; const COLOR_ENEMY: &str = "\x1b[38;5;1m";
@@ -77,9 +108,8 @@ impl ConsoleInput {
let mut term = console::Term::stdout(); let mut term = console::Term::stdout();
let _ = term.clear_screen(); let _ = term.clear_screen();
let _ = term.write_fmt(format_args!( let _ = term.write_fmt(format_args!(
"{}Floor lv.{:2} - {}\n{other}\n", "{}{}\n{other}\n",
Self::floor_as_string(&floor), Self::floor_as_string(&floor),
floor.floor.get_level(),
Self::entity_as_string(floor.entity), Self::entity_as_string(floor.entity),
)); ));
} }
@@ -122,33 +152,19 @@ impl ConsoleInput {
.collect() .collect()
}); });
Self::box_of(size, iter).collect() let title = format!(" Floor lv.{:2} ", floor.floor.get_level());
} box_of(size, title, iter).collect()
/// todo!() add docs
fn box_of(size: usize, iter: impl Iterator<Item = String>) -> impl Iterator<Item = String> {
std::iter::once("".to_string())
.chain(std::iter::repeat("".to_string()).take(size + 2))
.chain(std::iter::once("\n".to_string()))
.chain(iter.map(|string| {
std::iter::once("".to_string())
.chain(std::iter::once(string))
.chain(std::iter::once("\n".to_string()))
.collect()
}))
.chain(std::iter::once("".to_string()))
.chain(std::iter::repeat("".to_string()).take(size + 2))
.chain(std::iter::once("\n".to_string()))
} }
} }
#[typetag::serde] #[typetag::serde]
impl Behavior for ConsoleInput { impl Behavior for ConsoleInput {
fn update(&self, floor: FloorView) { fn update(&mut self, floor: FloorView) {
self.print_floor(floor, "".to_string()); self.print_floor(floor, "".to_string());
} }
fn you_died(&self, floor: FloorView) { fn on_death(&mut self, floor: FloorView) {
self.print_floor(floor, "YOU DIED!".to_string()); self.print_floor(floor, "YOU DIED!".to_string());
} }
fn get_next_action(&self) -> Option<Action> { fn get_next_action(&mut self) -> Option<Action> {
let mut term = console::Term::stdout(); let mut term = console::Term::stdout();
let _ = term.write("Insert your action [wasd or space for nothing]: ".as_bytes()); let _ = term.write("Insert your action [wasd or space for nothing]: ".as_bytes());