Entities
- priority in config now works - changed behavior with mut self - modified box for the console
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
|
||||
@@ -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.\
|
||||
|
||||
@@ -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());
|
||||
|
||||
|
||||
Reference in New Issue
Block a user