diff --git a/Cargo.toml b/Cargo.toml index 4a20f91..f656a09 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,4 +8,6 @@ edition = "2021" [dependencies] rand = "0.8.5" rand_pcg = { version = "0.3.1", features = ["serde1"] } -serde = { version = "1.0.197", features = ["derive"] } +serde = { version = "1.0.197", features = ["derive", "rc"] } +typetag = "0.2.16" +dyn-clone = "1.0.17" diff --git a/src/es03_game.rs b/src/es03_game.rs index 063ae7d..510986b 100644 --- a/src/es03_game.rs +++ b/src/es03_game.rs @@ -1,4 +1,3 @@ -#![allow(unused)] /** Es.3 * Implementare una libreria che permetta di realizzare il seguente gioco. @@ -40,3 +39,11 @@ pub mod generator; pub mod cell; pub mod config; pub mod entities; + +pub fn start_game() { + let mut game = game::Rogue::new(); + loop { + println!("{}", game); + game.compute_turn(); + } +} \ No newline at end of file diff --git a/src/es03_game/cell.rs b/src/es03_game/cell.rs index 578e553..a416ea7 100644 --- a/src/es03_game/cell.rs +++ b/src/es03_game/cell.rs @@ -1,52 +1,93 @@ +use super::entities::{Action, Direction, Entity}; +use dyn_clone::{clone_trait_object, DynClone}; use rand::Rng; use serde::{Deserialize, Serialize}; -use super::{entities::{Direction, Entity}, game::Rogue}; - -#[derive(Clone, Copy, Deserialize, Serialize)] +#[derive(Clone, Deserialize, Serialize)] pub enum Cell { - Staricase, - Special(Effect), + Entance, + Exit, + Special(Box), Wall, Empty, } -#[derive(Clone, Copy, Serialize, Deserialize)] -pub enum Effect { - InstantDamage(i8), - TurnBasedDamage(u8, i8), - //DeBuff(i8, i8), - Confusion(u8), - //Custom(i16), -} - -const DELTA: i32 = 5; -impl Effect { - pub fn effect(&self, game: &mut Rogue, entity: &mut Entity) { - let floor = game.current_floor(); - let rng = floor.get_rng(); - - match *self { - Effect::InstantDamage(damage) => { - let damage = damage as i32; - let damage = damage + rng.gen_range(-DELTA..DELTA); - entity.health += damage; - } - Effect::Confusion(time) => { - if time > 0 { - entity.add_effect(Effect::Confusion(time - 1)); - let coin_flip = rng.gen_range(0..=1); - if coin_flip == 1 { - let random_direction = Direction::generate_random(rng); - entity.direction = random_direction; - } +impl Cell { + pub fn entity_over(&mut self, entity: &mut Entity) { + match self { + Cell::Special(effect) => { + entity.add_effect(effect.clone()); + if !effect.is_persistent() { + *self = Cell::Empty } } - _ => todo!() + Cell::Wall => { + entity.direction.invert(); + entity.position = entity.direction.move_from(entity.position); + } + _ => (), } } } -pub const POISON: Effect = Effect::InstantDamage(20); -pub const FOOD: Effect = Effect::InstantDamage(-20); -pub const CONFUSION: Effect = Effect::Confusion(u8::MAX); +#[typetag::serde(tag = "type")] +pub trait Effect: DynClone { + fn is_persistent(&self) -> bool; + fn apply_to(&self, entity: &mut Entity); +} +clone_trait_object!(Effect); + +#[derive(Clone, Serialize, Deserialize)] +pub struct InstantDamage(pub i32); +#[typetag::serde] +impl Effect for InstantDamage { + fn is_persistent(&self) -> bool { + false + } + fn apply_to(&self, entity: &mut Entity) { + entity.health += self.0; + } +} + +#[derive(Clone, Serialize, Deserialize)] +pub struct Confusion(pub u8); +#[typetag::serde] +impl Effect for Confusion { + fn is_persistent(&self) -> bool { + true + } + fn apply_to(&self, entity: &mut Entity) { + if self.0 > 0 { + let mut floor = entity.get_floor(); + let mut floor = floor.get(); + let rng = floor.get_rng(); + let coin_flip = rng.gen_range(0..=1); + if coin_flip == 1 { + let random_direction = Direction::random(rng); + entity.buffer = Action::Move(random_direction); + } + entity.add_effect(Box::new(Self(self.0 - 1))); + } + } +} + +#[derive(Clone, Serialize, Deserialize)] +pub struct TurnBasedDamage { + time: u8, + damage: i32, +} +#[typetag::serde] +impl Effect for TurnBasedDamage { + fn is_persistent(&self) -> bool { + false + } + fn apply_to(&self, entity: &mut Entity) { + if self.time > 0 { + entity.health += self.damage; + entity.add_effect(Box::new(Self { + time: self.time - 1, + damage: self.damage, + })); + } + } +} diff --git a/src/es03_game/config.rs b/src/es03_game/config.rs index efc7111..82c929f 100644 --- a/src/es03_game/config.rs +++ b/src/es03_game/config.rs @@ -1,6 +1,9 @@ - -use super::cell::{self, Effect}; +use super::{ + cell::{Confusion, Effect, InstantDamage}, + entities::Decider, +}; use serde::{Deserialize, Serialize}; +use std::ops::Range; /** * Struttura Config usata per definire il gioco, ha alcune cose utili @@ -10,26 +13,56 @@ use serde::{Deserialize, Serialize}; #[derive(Clone, Deserialize, Serialize)] pub struct Config { pub game_seed: u64, - pub floor_size_range: (usize, usize), - pub effects: Vec, + pub room_size: Range, + pub floor_size: Range, pub effects_total: usize, + pub effects: Vec, + pub entities_total: usize, + pub entities: Vec, } -#[derive(Clone, Copy, Deserialize, Serialize)] +#[derive(Clone, Deserialize, Serialize)] pub struct ConfigEffect { - pub effect: Effect, - pub first_floor: usize, - pub last_floor: usize, + pub floors: Range, + pub effect: Box, pub priority: usize, } +#[derive(Clone, Deserialize, Serialize)] +pub struct ConfigEntity { + pub floors: Range, + pub name: String, + pub decider: Box, + pub health: i32, + pub attack: i32, +} + impl Default for Config { fn default() -> Self { Self { game_seed: 0, - floor_size_range: (20, 30), - effects: vec![ConfigEffect { effect: cell::POISON, first_floor: 0, last_floor: 255, priority: 1 }], + room_size: 5..10, + floor_size: 30..40, + effects: vec![ + ConfigEffect { + effect: Box::new(InstantDamage(20)), + floors: 0..255, + priority: 1, + }, + ConfigEffect { + effect: Box::new(InstantDamage(-20)), + floors: 0..255, + priority: 1, + }, + ConfigEffect { + effect: Box::new(Confusion(10)), + floors: 0..255, + priority: 1, + }, + ], effects_total: 45, + entities: vec![], + entities_total: 0, } } } diff --git a/src/es03_game/entities.rs b/src/es03_game/entities.rs index 45e8bc3..8a0fb21 100644 --- a/src/es03_game/entities.rs +++ b/src/es03_game/entities.rs @@ -1,15 +1,14 @@ -use std::{collections::VecDeque, fmt::Display}; - +use super::{cell::Effect, floor::FloorPtr}; +use dyn_clone::{clone_trait_object, DynClone}; use rand::Rng; use rand_pcg::Pcg32; use serde::{Deserialize, Serialize}; - -use super::{ - cell::{Cell, Effect}, - game::Rogue, -}; +use std::{collections::VecDeque, fmt::Display}; #[derive(Clone, Copy, Deserialize, Serialize)] +pub struct Position(pub usize, pub usize); + +#[derive(Clone, Deserialize, Serialize)] pub enum Direction { UP, DOWN, @@ -19,25 +18,25 @@ pub enum Direction { } impl Direction { - pub fn invert(&self) -> Self { - match *self { + pub fn invert(&mut self) { + *self = match *self { Direction::UP => Direction::DOWN, Direction::DOWN => Direction::UP, Direction::RIGHT => Direction::LEFT, Direction::LEFT => Direction::RIGHT, _ => Direction::NONE, - } + }; } - pub fn move_from(&self, pos: (usize, usize)) -> (usize, usize) { + pub fn move_from(&self, pos: Position) -> Position { match *self { - Direction::UP => (pos.0, pos.1 + 1), - Direction::DOWN => (pos.0, pos.1 - 1), - Direction::RIGHT => (pos.0 + 1, pos.1), - Direction::LEFT => (pos.0 - 1, pos.1), - Direction::NONE => (pos.0, pos.1), + Direction::UP => Position(pos.0, pos.1 + 1), + Direction::DOWN => Position(pos.0, pos.1 - 1), + Direction::RIGHT => Position(pos.0 + 1, pos.1), + Direction::LEFT => Position(pos.0 - 1, pos.1), + Direction::NONE => pos, } } - pub fn generate_random(rng: &mut Pcg32) -> Self { + pub fn random(rng: &mut Pcg32) -> Self { match rng.gen_range(0..4) { 0 => Direction::UP, 1 => Direction::DOWN, @@ -61,83 +60,103 @@ impl Display for Direction { } } -#[derive(Clone, Copy, Deserialize, Serialize)] +#[derive(Clone, Deserialize, Serialize)] +pub struct Entity { + name: String, + effects: VecDeque>, + decider: Box, + floor: FloorPtr, + pub buffer: Action, + pub position: Position, + pub direction: Direction, + pub health: i32, + attack: i32, +} + +impl Entity { + pub fn new(name: String, decider: Box, mut floor: FloorPtr) -> Self { + let position = floor.get().get_entrance(); + Self { + name, + floor, + decider, + position, + buffer: Action::DoNothing, + effects: VecDeque::new(), + direction: Direction::NONE, + attack: 100, + health: 100, + } + } + pub fn add_effect(&mut self, effect: Box) { + self.effects.push_back(effect); + } + pub fn get_floor(&self) -> FloorPtr { + self.floor.clone() + } + pub fn set_floor(&mut self, floor: FloorPtr) { + self.floor = floor; + self.position = self.floor.get().get_entrance(); + } + pub fn update(&mut self) { + self.compute_action(); + self.compute_effects(); + } + + fn compute_effects(&mut self) { + let total = self.effects.len(); // len could change + for _ in 0..total { + if let Some(effect) = self.effects.pop_front() { + effect.apply_to(self); + } + } + } + fn compute_action(&mut self) { + let action = self.decider.get_next_action(); + match self.buffer { + Action::DoNothing => action.apply(self), + _ => (), + } + self.buffer = Action::DoNothing; + } +} + +#[derive(Clone, Deserialize, Serialize)] pub enum Action { Move(Direction), // attack DoNothing, } -#[derive(Deserialize, Serialize)] -pub struct Entity { - name: String, - floor: usize, - effected_by: VecDeque, - player: bool, - pub position: (usize, usize), - pub direction: Direction, - pub health: i32, - pub attack: i32, -} - -impl Entity { - pub fn new(name: String) -> Self { - Self { - name, - floor: 0, - effected_by: VecDeque::new(), - position: (0, 0), - player: false, - direction: Direction::UP, - attack: 100, - health: 100, - } - } - pub fn new_player(name: String) -> Self { - let mut player = Self::new(name); - player.player = true; - player - } - pub fn add_effect(&mut self, effect: Effect) { - self.effected_by.push_back(effect); - } - pub fn compute_effects(&mut self, game: &mut Rogue) { - let total = self.effected_by.len(); // len could change - for _ in 0..total { - if let Some(effect) = self.effected_by.pop_front() { - effect.effect(game, self); - } - } - } - pub fn do_action(&mut self, game: &mut Rogue, action: Action) { - match action { +impl Action { + pub fn apply(self, entity: &mut Entity) { + match self { + Action::DoNothing => {} Action::Move(direction) => { - self.direction = direction; - self.do_action_move(game); - } - _ => todo!(), - } - } + let pos = direction.move_from(entity.position); + entity.direction = direction; + entity.position = pos; - fn do_action_move(&mut self, game: &mut Rogue) { - let direction = self.direction; - let pos = direction.move_from(self.position); - let floor = game.current_floor(); - - match floor.get_cell(pos) { - Cell::Empty => { - self.position = pos; + let mut floor = entity.floor.clone(); + let mut floor = floor.get(); + let cell = floor.get_cell(pos); + cell.entity_over(entity); } - Cell::Special(effect) => { - self.position = pos; - self.add_effect(effect) - } - Cell::Staricase => { - if self.player { - game.build_new_floor() - } - } - Cell::Wall => self.direction = direction.invert(), } } } + +clone_trait_object!(Decider); +#[typetag::serde(tag = "type")] +pub trait Decider: DynClone { + fn get_next_action(&self) -> Action; +} + +#[derive(Clone, Serialize, Deserialize)] +pub struct Immovable; +#[typetag::serde] +impl Decider for Immovable { + fn get_next_action(&self) -> Action { + Action::DoNothing + } +} diff --git a/src/es03_game/floor.rs b/src/es03_game/floor.rs index 6727ced..ef4ddb3 100644 --- a/src/es03_game/floor.rs +++ b/src/es03_game/floor.rs @@ -1,12 +1,28 @@ +use super::{ + cell::Cell, + entities::{Entity, Position}, +}; use rand_pcg::Pcg32; use serde::{Deserialize, Serialize}; +use std::{ + cell::{RefCell, RefMut}, + rc::Rc, +}; -use super::cell::Cell; -use super::entities::Entity; -use super::game::Rogue; -use super::generator::Generator; +#[derive(Clone, Deserialize, Serialize)] +pub struct FloorPtr(Rc>); +impl FloorPtr { + pub fn new(level: usize, rng: Pcg32, entities: Vec, grid: Vec>) -> Self { + Self(Rc::new(RefCell::new(Floor::new( + level, rng, entities, grid, + )))) + } + pub fn get(&mut self) -> RefMut { + self.0.borrow_mut() + } +} -#[derive(Deserialize, Serialize)] +#[derive(Clone, Deserialize, Serialize)] pub struct Floor { level: usize, grid: Vec>, @@ -15,28 +31,43 @@ pub struct Floor { } impl Floor { - pub fn new(generator: Generator) -> Self { + fn new(level: usize, rng: Pcg32, entities: Vec, grid: Vec>) -> Self { Self { - entities: vec![], - level: generator.level, - grid: generator.build_empty_matrix(), - rng: generator.rng, + level, + grid, + entities, + rng, } } + + pub fn get_level(&self) -> usize { + self.level + } pub fn get_rng(&mut self) -> &mut Pcg32 { &mut self.rng } - pub fn get_level(&self) -> usize { - self.level + pub fn get_cell(&mut self, pos: Position) -> &mut Cell { + &mut self.grid[pos.0][pos.1] } - pub fn get_cell(&self, pos: (usize, usize)) -> Cell { - self.grid[pos.0][pos.1] + pub fn get_entrance(&mut self) -> Position { + self.grid + .iter() + .enumerate() + .find_map(|(x, vec)| { + vec.iter().enumerate().find_map(|(y, cell)| { + if let Cell::Entance = cell { + Some(Position(x, y)) + } else { + None + } + }) + }) + .expect("Entrance of the floor should be inside the grid!") } - pub fn compute_entities(&mut self, game: &mut Rogue) { + pub fn update_entities(&mut self) { for entity in &mut self.entities { - entity.compute_effects(game); - entity.do_action(game, game.input_action(entity)); + entity.update(); } } } diff --git a/src/es03_game/game.rs b/src/es03_game/game.rs index 5c8f56d..25a0a62 100644 --- a/src/es03_game/game.rs +++ b/src/es03_game/game.rs @@ -1,19 +1,24 @@ -use std::fmt::Display; - +use super::{ + cell::Cell, + config::Config, + entities::{Entity, Immovable}, + floor::FloorPtr, + generator::Generator, +}; use rand::{RngCore, SeedableRng}; use rand_pcg::Pcg32; use serde::{Deserialize, Serialize}; - -use super::{config::Config, entities::{Action, Entity}, floor::Floor, generator::Generator}; +use std::fmt::Display; /** * Struttura del gioco generico che implementa un RogueLike. */ -#[derive(Deserialize, Serialize)] +#[derive(Clone, Deserialize, Serialize)] pub struct Rogue { - floor: Floor, + floors: Vec, rng: Pcg32, config: Config, + players: Vec, } impl Display for Rogue { @@ -24,30 +29,75 @@ impl Display for Rogue { impl Rogue { pub fn new() -> Self { - let config = Config::default(); - let mut rng = Pcg32::seed_from_u64(config.game_seed); - let floor = Floor::new(Generator::new(rng.next_u64(), 0, &config)); - - Self { rng, config, floor } + Self::new_with(Config::default()) } - - pub fn current_floor(&mut self) -> &mut Floor { - &mut self.floor + pub fn new_with(config: Config) -> Self { + let mut game = Self { + rng: Pcg32::seed_from_u64(config.game_seed), + floors: vec![], + players: vec![], + config, + }; + game.build_next_floor(); + game } - - pub fn build_new_floor(&mut self) { - let level = self.floor.get_level(); + pub fn add_player(&mut self, name: String) { + let floor = self.floors[0].clone(); + let decider = Box::new(Immovable); + let entity = Entity::new(name, decider, floor); + self.players.push(entity); + } + pub fn get_floor(&self, level: usize) -> FloorPtr { + let floors = self.floors.len() - 1; + let index = level.min(floors); + self.floors[index].clone() + } + pub fn build_next_floor(&mut self) { let floor_seed = self.rng.next_u64(); - - let generator = Generator::new(floor_seed, level + 1, &self.config); - self.floor = Floor::new(generator); + let floor_level = self.floors.len(); + let generator = Generator::new(floor_seed, floor_level, &self.config); + let floor = generator.build_floor(); + self.floors.push(floor); } - - pub fn input_action(&self, entity: &Entity) -> Action { - todo!() - } - pub fn compute_turn(&mut self) { - todo!(); + let mut update_floors = vec![false; self.floors.len()]; + let mut change_floors = vec![0; self.players.len()]; + + self.players.iter_mut().enumerate().for_each(|(i, player)| { + let mut floor = player.get_floor(); + let mut floor = floor.get(); + + player.update(); + update_floors[floor.get_level()] = true; + if let Cell::Exit = floor.get_cell(player.position) { + change_floors[i] = floor.get_level() + 1; + } + }); + + update_floors + .iter() + .enumerate() + .filter_map(|(i, b)| if *b { Some(i) } else { None }) + .for_each(|i| self.floors[i].get().update_entities()); + change_floors + .iter() + .enumerate() + .filter(|(_, f)| **f != 0) + .for_each(|(player, floor)| { + let floor = self.get_floor_or_build(*floor); + let player = &mut self.players[player]; + player.set_floor(floor); + }); + } + fn get_floor_or_build(&mut self, level: usize) -> FloorPtr { + let mut level = level; + if level > self.floors.len() { + level = self.floors.len(); + } + if level == self.floors.len() { + self.build_next_floor() + } + + self.get_floor(level) } } diff --git a/src/es03_game/generator.rs b/src/es03_game/generator.rs index 8df09bf..33db08b 100644 --- a/src/es03_game/generator.rs +++ b/src/es03_game/generator.rs @@ -1,61 +1,113 @@ -use rand::Rng; -use rand::SeedableRng; +use super::{ + cell::Cell, + config::{Config, ConfigEffect, ConfigEntity}, + floor::{Floor, FloorPtr}, +}; +use rand::{Rng, SeedableRng}; use rand_pcg::Pcg32; +use std::ops::Range; -use super::cell::Cell; - -use super::config::Config; -use super::config::ConfigEffect; - -pub struct Generator<'a> { +pub struct Generator { pub rng: Pcg32, pub level: usize, - size: usize, - effects: Vec<&'a ConfigEffect>, + size_rooms: Range, effects_total: usize, - // enemies vec configuration? + effects: Vec, + entities_total: usize, + entities: Vec, + size: usize, + grid: Vec>, // enemies vec configuration? } -impl<'a> Generator<'a> { - pub fn new(floor_seed: u64, floor_level: usize, config: &'a Config) -> Self { +impl Generator { + pub fn new(floor_seed: u64, floor_level: usize, config: &Config) -> Self { let mut rand_pcg = Pcg32::seed_from_u64(floor_seed); - let range = config.floor_size_range.0..config.floor_size_range.1; - let floor_size = rand_pcg.gen_range(range); - - let effects_list = &config.effects; - let effects_list = effects_list.into_iter(); - let effects_list = effects_list.filter_map(|val| { - if floor_level >= val.first_floor && floor_level <= val.last_floor { - Some(val) - } else { - None - } - }); - let effects_list = effects_list.collect(); - + let floor_size = rand_pcg.gen_range(config.floor_size.clone()); Self { rng: rand_pcg, level: floor_level, size: floor_size, + size_rooms: config.room_size.clone(), effects_total: config.effects_total, - effects: effects_list, + effects: Self::clone_vec_filter(&config.effects, |val| { + val.floors.contains(&floor_level) + }), + entities_total: config.entities_total, + entities: Self::clone_vec_filter(&config.entities, |val| { + val.floors.contains(&floor_level) + }), + grid: Self::grid_with_only(floor_size, Cell::Wall), } } - pub fn build_empty_matrix(&self) -> Vec> { - self.build_matrix_with(Cell::Empty) + pub fn build_floor(mut self) -> FloorPtr { + for x in 1..(self.size - 1) { + for y in 1..(self.size - 1) { + self.grid[x][y] = Cell::Empty; + } + } + + self.rand_place_cell(Cell::Entance); + self.rand_place_effects(); + FloorPtr::new(self.level, self.rng, vec![], self.grid) } - pub fn build_labyrinth(&mut self) -> Vec> { - todo!() - } - pub fn build_rooms(&mut self) -> Vec> { + pub fn build_floor_catacomb(mut self) -> Floor { + // init to WALLS + // reserve some cells for rooms ?? todo!() } - fn place_staircase(&mut self, grid: Vec>) -> Vec> { + fn build_rooms(&mut self) { todo!() } - fn build_matrix_with(&self, cell: Cell) -> Vec> { - vec![vec![cell; self.size]; self.size] + fn rand_build_room(&mut self) -> (usize, usize, usize, usize) { + loop { + let (x, y) = self.rand_2d(0..self.size); + let size = self.rand_2d(self.size_rooms.clone()); + let (x_up, y_up) = (x + size.0, y + size.1); + + if x_up < self.size && y_up < self.size { + for x in x..x_up { + for y in y..y_up { + self.grid[x][y] = Cell::Empty + } + } + return (x, y, x_up, y_up); + } + } + } + + fn rand_place_effects(&mut self) { + for _ in 0..self.effects_total { + let index = self.rng.gen_range(0..self.effects.len()); + let effect = self.effects[index].effect.clone(); + let cell = Cell::Special(effect); + self.rand_place_cell(cell); + } + } + fn rand_place_cell(&mut self, cell: Cell) -> (usize, usize) { + loop { + let (x, y) = self.rand_2d(0..self.size); + if let Cell::Empty = self.grid[x][y] { + self.grid[x][y] = cell; + return (x, y); + } + } + } + fn rand_2d(&mut self, range: Range) -> (usize, usize) { + let x = self.rng.gen_range(range.clone()); + let y = self.rng.gen_range(range); + (x, y) + } + + fn clone_vec_filter(original: &Vec, filter: impl Fn(&T) -> bool) -> Vec { + original + .clone() + .into_iter() + .filter_map(|val| if filter(&val) { Some(val) } else { None }) + .collect() + } + fn grid_with_only(size: usize, cell: Cell) -> Vec> { + vec![vec![cell; size]; size] } }