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

View File

@@ -1,9 +1,9 @@
use super::{
cell::Effect,
cell::{Cell, Effect},
floor::{Floor, FloorView},
};
use dyn_clone::{clone_trait_object, DynClone};
use rand::Rng;
use rand::{Rng, SeedableRng};
use rand_pcg::Pcg32;
use serde::{Deserialize, Serialize};
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.\
/// È 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 {
Up,
Down,
Left,
Right,
#[default]
None,
}
@@ -89,7 +90,7 @@ impl Display for Direction {
pub struct Entity {
name: String,
effects: VecDeque<Box<dyn Effect>>,
behavior: Box<dyn Behavior>,
behavior: Option<Box<dyn Behavior>>,
pub buffer: Action,
pub position: Position,
pub direction: Direction,
@@ -106,7 +107,7 @@ impl Entity {
pub fn new(name: String, health: i32, attack: i32, behavior: Box<dyn Behavior>) -> Self {
Self {
name,
behavior,
behavior: Some(behavior),
position: Position(0, 0),
attack,
health,
@@ -164,33 +165,41 @@ impl Entity {
/// 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.\
/// 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
/// e non permetterà all'entità di fare update.\
/// Nel caso in cui l'entità non riesca a fare l'update viene ritornato false.\
/// Nel caso in cui l'entità non sia più in vita questo metodo ritornerà None
/// e l' entità smetterà di esistere.\
/// Nel caso in cui l'entità non riesca a fare l'update viene ritornato None.\
/// 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() {
self.behavior.you_died(floor.get_limited_view_floor(self));
return false;
return self.die(behavior, floor);
}
self.behavior.update(floor.get_limited_view_floor(self));
let action = self.compute_action(floor);
behavior.update(floor.get_limited_view_floor(&self));
let action = self.compute_action(&mut behavior, floor);
if action.is_none() {
return false;
return None;
}
if !self.is_alive() {
self.behavior.you_died(floor.get_limited_view_floor(self));
return false;
return self.die(behavior, floor);
}
self.compute_effects(floor);
if !self.is_alive() {
self.behavior.you_died(floor.get_limited_view_floor(self));
return false
return self.die(behavior, floor);
}
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à.
@@ -204,8 +213,8 @@ impl Entity {
}
/// prende una decisione e applica l'azione da fare
/// L'azione compiuta viene restituita, altrimenti None
fn compute_action(&mut self, floor: &mut Floor) -> Option<Action> {
let action = self.behavior.get_next_action()?;
fn compute_action(&mut self, behavior: &mut Box<dyn Behavior>, floor: &mut Floor) -> Option<Action> {
let action = behavior.get_next_action()?;
let action = match self.buffer {
Action::DoNothing => action,
_ => mem::replace(&mut self.buffer, Action::DoNothing),
@@ -236,10 +245,11 @@ 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, Debug, Deserialize, Serialize)]
#[derive(Clone, Default, Debug, Deserialize, Serialize)]
pub enum Action {
Move(Direction),
//Attack(Direction),
#[default]
DoNothing,
}
@@ -257,7 +267,7 @@ impl Action {
direction.move_from(&mut entity.position);
entity.direction = direction;
let cell = floor.get_cell(&entity.position);
let cell = floor.get_cell_mut(&entity.position);
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
/// 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);
/// o di registrare dei valori per l'algoritmo di generazione delle azioni.\
/// Non è necessario implementarla.
fn update(&mut self, _view: 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);
/// in modo che possa eventualmente fare ulteriori calcoli.\
/// Non è necessario implementarla.
fn on_death(&mut self, _view: 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.\
@@ -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.\
/// 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>;
fn get_next_action(&mut self) -> Option<Action>;
}
clone_trait_object!(Behavior);
@@ -306,9 +318,37 @@ clone_trait_object!(Behavior);
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> {
fn get_next_action(&mut self) -> Option<Action> {
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
/// 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(&mut self, pos: &Position) -> &mut Cell {
pub fn get_cell_mut(&mut self, pos: &Position) -> &mut Cell {
let len = self.grid.len() - 1;
let x = pos.0.min(len);
let y = pos.1.min(len);
&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.\
/// Utile come spawn per quando i giocatori arrivano al piano.
pub fn get_entrance(&mut self) -> Position {
@@ -93,20 +105,6 @@ impl Floor {
.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.\
/// Nel caso in cui non ci siano giocatori sopra, questo metodo ritornerà None.
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
pub fn update_entities(&mut self) {
for _ in 0..self.entities.len() {
let mut entity = self.entities.pop_front().unwrap();
if entity.update(self) {
let entity = self.entities.pop_front().unwrap();
if let Some(entity) = entity.update(self) {
self.entities.push_back(entity);
}
}

View File

@@ -23,7 +23,7 @@ pub struct 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 {
Self::new_with(Config::default())
}

View File

@@ -74,16 +74,20 @@ impl<'a> Generator<'a> {
self.rand_place_effects(&mut 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.\
/// essi vengono piazzati solamente sulle celle Empty
fn rand_place_effects(&mut self, grid: &mut Vec<Vec<Cell>>) {
let total = self.config.effects_total;
let original = &self.config.effects;
let effects = Self::vec_filter(original, |val| val.floors.contains(&self.level));
let effects = vec_filter(&self.config.effects, |e| {
e.floors.contains(&self.level).then(|| (e.priority, e))
});
for _ in 0..total {
let index = self.rng.gen_range(0..effects.len());
let effect = effects[index].effect.clone();
for _ in 0..self.config.effects_total {
let effect = vec_get_sample(&effects, &mut self.rng).effect.clone();
let cell = Cell::Special(effect);
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
.clone()
.into_iter()
.filter_map(|val| filter(&val).then(|| val))
.collect()
}
}
/// crea una vista del vettore passato in input dopo aver applicato la funzione di filtro
pub fn vec_filter<T, F>(original: &Vec<T>, filter: F) -> Vec<(f32, &T)>
where
F: FnMut(&T) -> Option<(u32, &T)>,
{
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.\

View File

@@ -56,10 +56,41 @@ pub fn run_console(player: String, seed: u64) {
game.add_player(player, Box::new(ConsoleInput));
while game.has_players() {
let _ = game.save("save.json");
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_EFFECT: &str = "\x1b[95m";
const COLOR_ENEMY: &str = "\x1b[38;5;1m";
@@ -77,9 +108,8 @@ impl ConsoleInput {
let mut term = console::Term::stdout();
let _ = term.clear_screen();
let _ = term.write_fmt(format_args!(
"{}Floor lv.{:2} - {}\n{other}\n",
"{}{}\n{other}\n",
Self::floor_as_string(&floor),
floor.floor.get_level(),
Self::entity_as_string(floor.entity),
));
}
@@ -122,33 +152,19 @@ impl ConsoleInput {
.collect()
});
Self::box_of(size, 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()))
let title = format!(" Floor lv.{:2} ", floor.floor.get_level());
box_of(size, title, iter).collect()
}
}
#[typetag::serde]
impl Behavior for ConsoleInput {
fn update(&self, floor: FloorView) {
fn update(&mut self, floor: FloorView) {
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());
}
fn get_next_action(&self) -> Option<Action> {
fn get_next_action(&mut self) -> Option<Action> {
let mut term = console::Term::stdout();
let _ = term.write("Insert your action [wasd or space for nothing]: ".as_bytes());