MultiFloor

- game now has multi-floor enabled
- changed logic of update_players
This commit is contained in:
2024-05-19 19:58:08 +02:00
parent ba7051ef07
commit fd23efdb29
5 changed files with 130 additions and 68 deletions

View File

@@ -159,12 +159,24 @@ impl Entity {
/// Nel caso in cui l'entità non riesca a fare l'update viene ritornato false.\
/// Cio significa che l'entità verrà rimossa dal gioco.
pub fn update(&mut self, floor: &mut Floor) -> bool {
self.behavior.update(floor.get_limited_view_floor(self));
if self.is_alive() && matches!(self.compute_action(floor), Some(_)) {
self.compute_effects(floor);
return true;
if !self.is_alive() {
self.behavior.you_died(floor.get_limited_view_floor(self));
return false;
}
false
self.behavior.update(floor.get_limited_view_floor(self));
let action = self.compute_action(floor);
if action.is_none() {
return false;
}
if !self.is_alive() {
self.behavior.you_died(floor.get_limited_view_floor(self));
return false;
}
self.compute_effects(floor);
true
}
/// calcola gli effetti e li applica all'entità.
@@ -231,7 +243,7 @@ impl Action {
direction.move_from(&mut entity.position);
entity.direction = direction;
let cell = floor.get_cell(&mut entity.position);
let cell = floor.get_cell(&entity.position);
cell.entity_over(entity);
}
}

View File

@@ -68,11 +68,11 @@ 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: &mut Position) -> &mut Cell {
pub fn get_cell(&mut self, pos: &Position) -> &mut Cell {
let len = self.grid.len() - 1;
pos.0 = pos.0.min(len);
pos.1 = pos.1.min(len);
&mut self.grid[pos.0][pos.1]
let x = pos.0.min(len);
let y = pos.1.min(len);
&mut self.grid[x][y]
}
/// Restituisce la posizione dell'entrata del piano.\
@@ -95,16 +95,39 @@ impl Floor {
/// 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 remove = vec![];
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 {
remove.push(player);
next_floor.push(player);
}
}
remove
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> {
let index = self
.players
.iter()
.enumerate()
.filter_map(|(i, player)| {
let pos = &player.position;
match &self.grid[pos.0][pos.1] {
Cell::Exit => Some(i),
_ => None,
}
})
.next();
if let Some(i) = index {
self.players.remove(i)
} else {
None
}
}
/// Fa l'update di tutte le entità e rimuove eventualmente quelle non più in vita
@@ -218,27 +241,6 @@ impl<'a> FloorView<'a> {
})
.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())
}
}
impl<'a> Display for FloorView<'a> {

View File

@@ -90,23 +90,24 @@ impl Dungeon {
/// - Update di tutte le entità del piano
/// - Modifica di piano di eventuali giocatori
pub fn compute_turn(&mut self) {
let moved: Option<Vec<Entity>> = self.floors.iter_mut().fold(None, |moved, floor| {
let removed = floor.has_players().then(|| {
let removed = floor.update_players();
let moved = self.floors.iter_mut().fold(None, |moved, floor| {
if floor.has_players() {
let _ = floor.update_players(); //todo!() evantually return the dead players? idk
floor.update_entities();
removed
});
if let Some(mut moved) = moved {
moved.drain(..).for_each(|player| floor.add_player(player));
}
removed
if let Some(player) = moved {
floor.add_player(player);
}
floor.get_player_at_exit()
});
if let Some(mut moved) = moved {
if let Some(player) = moved {
self.build_next_floor();
let len = self.floors.len();
let floor = &mut self.floors[len - 1];
moved.drain(..).for_each(|player| floor.add_player(player));
floor.add_player(player);
}
}

View File

@@ -56,14 +56,21 @@ impl<'a> Generator<'a> {
pub fn build_floor(mut self) -> Floor {
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)
let mut gen = MazeGenerator::new(self.size, room_size, &mut self.rng);
let mut grid = gen
.generate_rooms(maze_gen.room_placing_attempts)
.generate_labyrinth(maze_gen.straight_percentage)
.connect_regions()
.remove_dead_ends(maze_gen.dead_ends)
.finalize(Cell::Wall, Cell::Empty);
self.rand_place(&mut grid, Cell::Entance);
let index = gen.get_random_room_index();
let entrance = gen.get_room_ranges(index);
let index = gen.get_random_room_index();
let exit = gen.get_room_ranges(index);
self.rand_place(&mut grid, Cell::Entance, entrance.0, entrance.1);
self.rand_place(&mut grid, Cell::Exit, exit.0, exit.1);
self.rand_place_effects(&mut grid);
Floor::new(self.level, self.rng, vec![], grid)
}
@@ -78,18 +85,24 @@ impl<'a> Generator<'a> {
let index = self.rng.gen_range(0..effects.len());
let effect = effects[index].effect.clone();
let cell = Cell::Special(effect);
self.rand_place(grid, cell);
self.rand_place(grid, cell, 0..self.size, 0..self.size);
}
}
/// piazza una cella in un punto casuale del piano.\
/// il metodo contiuna a provare a piazzare la cella finche non trova una cella Empty.
fn rand_place(&mut self, grid: &mut Vec<Vec<Cell>>, cell: Cell) -> (usize, usize) {
fn rand_place(
&mut self,
grid: &mut Vec<Vec<Cell>>,
cell: Cell,
range_x: Range<usize>,
range_y: Range<usize>,
) -> Position {
loop {
let x = self.rng.gen_range(0..self.size.clone());
let y = self.rng.gen_range(0..self.size);
let x = self.rng.gen_range(range_x.clone());
let y = self.rng.gen_range(range_y.clone());
if let Cell::Empty = grid[x][y] {
grid[x][y] = cell;
return (x, y);
return Position(x, y);
}
}
}
@@ -183,7 +196,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: u32) -> Self {
pub fn remove_dead_ends(&mut self, cutoff: u32) -> &mut Self {
let mut dead_ends = (0..self.size)
.into_iter()
.flat_map(|x| {
@@ -215,7 +228,7 @@ impl<'a> MazeGenerator<'a> {
/// Questa funzione serve per fare proprio quello, ovvero il collegamento fra di essi.\
/// Il labirinto si può vedere come un grafo nel quale ci sono delle regioni (stanze e corridoi) scollegate
/// fra di loro, e l'unico modo per metterle assieme è quello di preare degli archi (rompere i muri).\
pub fn connect_regions(mut self) -> Self {
pub fn connect_regions(&mut self) -> &mut Self {
let mut connectors = self.get_regions_connectors();
let mut merged = MergeSets::new(1, self.current_region);
let mut keys = connectors.keys().map(|pos| pos.clone()).collect::<Vec<_>>();
@@ -272,7 +285,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: u32) -> Self {
pub fn generate_labyrinth(&mut self, mut straight_percentage: u32) -> &mut Self {
straight_percentage = straight_percentage.min(100); // cap at 100
for x in (1..self.size).step_by(2) {
@@ -347,7 +360,7 @@ impl<'a> MazeGenerator<'a> {
/// Nel caso in cui questo metodo venga chiamato dopo la generazione del labirinto, e le stanze venissero
/// inserite senza collisioni con quelle precedenti, il labirinto sottostante sarebbe sovrascritto.\
/// Il parametro attempts indica dopo quanti inserimenti falliti si deve fermare.
pub fn generate_rooms(mut self, mut attempts: u32) -> Self {
pub fn generate_rooms(&mut self, mut attempts: u32) -> &mut Self {
while attempts > 0 {
let room = Room::rand(self.rng, self.size, self.rooms_size.clone());
if self.rooms.iter().any(|other| room.collide(other)) {
@@ -392,6 +405,17 @@ impl<'a> MazeGenerator<'a> {
fn get(&self, pos: &Position) -> Option<usize> {
self.regions[pos.0][pos.1]
}
/// Ritorna un indice a caso fra quelli possibili riguardo le stanze create.
pub fn get_random_room_index(&mut self) -> usize {
self.rng.gen_range(0..self.rooms.len())
}
/// Ritorna una coppia di ranges che indicano la zona in cui si trova la stanza indicata fra quelle generate.
pub fn get_room_ranges(&self, index: usize) -> (Range<usize>, Range<usize>) {
let room = &self.rooms[index.min(self.rooms.len())];
let x = room.lo.0..(room.hi.0 + 1);
let y = room.lo.1..(room.hi.1 + 1);
(x, y)
}
}
/// Struttura ausiliaria usata per contenere le posizioni.\

View File

@@ -48,9 +48,9 @@ pub mod generator;
* 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) {
pub fn run_console(player: String, seed: u64) {
let mut config = Config::default();
config.game_seed = rand::random();
config.game_seed = seed;
let mut game = Dungeon::new_with(config);
game.add_player(player, Box::new(ConsoleInput));
@@ -64,6 +64,17 @@ pub fn run_console(player: String) {
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ConsoleInput;
impl ConsoleInput {
fn print_floor(&self, floor: FloorView, other:String) {
let mut term = console::Term::stdout();
let _ = term.clear_screen();
let _ = term.write_fmt(format_args!(
"{}{}\n{}\n",
Self::floor_as_string(&floor),
floor.entity,
other
));
}
/// todo!() add docs
fn floor_as_string(floor: &FloorView) -> String {
let view = 5;
let size = (2 * view) * 3;
@@ -82,24 +93,36 @@ impl ConsoleInput {
})
});
FloorView::box_of(size, iter).collect()
Self::box_of(size, iter).collect()
}
/// todo!() add docs
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())
}
}
#[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
));
self.print_floor(floor, "".to_string());
}
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));
self.print_floor(floor, "YOU DIED!".to_string());
}
fn get_next_action(&self) -> Option<Action> {
let mut term = console::Term::stdout();