diff --git a/src/es03_game.rs b/src/es03_game.rs deleted file mode 100644 index 9e937e1..0000000 --- a/src/es03_game.rs +++ /dev/null @@ -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; diff --git a/src/es03_game/cell.rs b/src/es03_game/cell.rs index 571f800..97628f0 100644 --- a/src/es03_game/cell.rs +++ b/src/es03_game/cell.rs @@ -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, diff --git a/src/es03_game/config.rs b/src/es03_game/config.rs index bdace44..1028c11 100644 --- a/src/es03_game/config.rs +++ b/src/es03_game/config.rs @@ -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, pub room_size: Range, @@ -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, pub effect: Box, - 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, pub name: String, pub decider: Box, 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, }, } } diff --git a/src/es03_game/entities.rs b/src/es03_game/entities.rs index c3b62b7..5ccc681 100644 --- a/src/es03_game/entities.rs +++ b/src/es03_game/entities.rs @@ -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>, @@ -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 { 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 { - 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)), - _ => (), - } - } - } - } -} diff --git a/src/es03_game/floor.rs b/src/es03_game/floor.rs index 50daced..b1bf9a9 100644 --- a/src/es03_game/floor.rs +++ b/src/es03_game/floor.rs @@ -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>, @@ -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 + '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> { + let grid = &self.floor.grid; + let entities = self + .floor + .get_all_entities() + .chain(std::iter::once(self.entity)) + .map(|entity| (&entity.position, entity)) + .collect::>(); + 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> { - 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::>() - }) - .collect::>(); + 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::>() + }) + .collect::>() + } + + /// todo!() add docs + pub fn box_of(size: usize, iter: impl Iterator) -> impl Iterator { + 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::()) .collect(); diff --git a/src/es03_game/game.rs b/src/es03_game/game.rs index fa1732c..16741f8 100644 --- a/src/es03_game/game.rs +++ b/src/es03_game/game.rs @@ -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, config: Config, diff --git a/src/es03_game/generator.rs b/src/es03_game/generator.rs index f07ea9f..149896c 100644 --- a/src/es03_game/generator.rs +++ b/src/es03_game/generator.rs @@ -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::>(); 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, diff --git a/src/es03_game/mod.rs b/src/es03_game/mod.rs new file mode 100644 index 0000000..9a7cf20 --- /dev/null +++ b/src/es03_game/mod.rs @@ -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 { + 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)), + _ => (), + } + } + } + } +}