diff --git a/src/es03_game.rs b/src/es03_game.rs new file mode 100644 index 0000000..063ae7d --- /dev/null +++ b/src/es03_game.rs @@ -0,0 +1,42 @@ +#![allow(unused)] + +/** 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 new file mode 100644 index 0000000..578e553 --- /dev/null +++ b/src/es03_game/cell.rs @@ -0,0 +1,52 @@ +use rand::Rng; +use serde::{Deserialize, Serialize}; + +use super::{entities::{Direction, Entity}, game::Rogue}; + +#[derive(Clone, Copy, Deserialize, Serialize)] +pub enum Cell { + Staricase, + Special(Effect), + 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; + } + } + } + _ => todo!() + } + } +} + +pub const POISON: Effect = Effect::InstantDamage(20); +pub const FOOD: Effect = Effect::InstantDamage(-20); +pub const CONFUSION: Effect = Effect::Confusion(u8::MAX); diff --git a/src/es03_game/config.rs b/src/es03_game/config.rs new file mode 100644 index 0000000..efc7111 --- /dev/null +++ b/src/es03_game/config.rs @@ -0,0 +1,35 @@ + +use super::cell::{self, Effect}; +use serde::{Deserialize, Serialize}; + +/** + * Struttura Config usata per definire il gioco, ha alcune cose utili + * TODO sarebbe bello poterle prendere da file. + */ + +#[derive(Clone, Deserialize, Serialize)] +pub struct Config { + pub game_seed: u64, + pub floor_size_range: (usize, usize), + pub effects: Vec, + pub effects_total: usize, +} + +#[derive(Clone, Copy, Deserialize, Serialize)] +pub struct ConfigEffect { + pub effect: Effect, + pub first_floor: usize, + pub last_floor: usize, + pub priority: usize, +} + +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 }], + effects_total: 45, + } + } +} diff --git a/src/es03_game/entities.rs b/src/es03_game/entities.rs new file mode 100644 index 0000000..45e8bc3 --- /dev/null +++ b/src/es03_game/entities.rs @@ -0,0 +1,143 @@ +use std::{collections::VecDeque, fmt::Display}; + +use rand::Rng; +use rand_pcg::Pcg32; +use serde::{Deserialize, Serialize}; + +use super::{ + cell::{Cell, Effect}, + game::Rogue, +}; + +#[derive(Clone, Copy, Deserialize, Serialize)] +pub enum Direction { + UP, + DOWN, + LEFT, + RIGHT, + NONE, +} + +impl Direction { + pub fn invert(&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) { + 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), + } + } + pub fn generate_random(rng: &mut Pcg32) -> Self { + match rng.gen_range(0..4) { + 0 => Direction::UP, + 1 => Direction::DOWN, + 2 => Direction::LEFT, + _ => Direction::RIGHT, + } + } +} + +impl Display for Direction { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let c = match self { + Self::UP => '▲', + Self::DOWN => '▼', + Self::LEFT => '◄', + Self::RIGHT => '►', + Self::NONE => '■', + }; + + write!(f, "{}", c) + } +} + +#[derive(Clone, Copy, 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 { + Action::Move(direction) => { + self.direction = direction; + self.do_action_move(game); + } + _ => todo!(), + } + } + + 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; + } + 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(), + } + } +} diff --git a/src/es03_game/floor.rs b/src/es03_game/floor.rs new file mode 100644 index 0000000..6727ced --- /dev/null +++ b/src/es03_game/floor.rs @@ -0,0 +1,42 @@ +use rand_pcg::Pcg32; +use serde::{Deserialize, Serialize}; + +use super::cell::Cell; +use super::entities::Entity; +use super::game::Rogue; +use super::generator::Generator; + +#[derive(Deserialize, Serialize)] +pub struct Floor { + level: usize, + grid: Vec>, + entities: Vec, + rng: Pcg32, +} + +impl Floor { + pub fn new(generator: Generator) -> Self { + Self { + entities: vec![], + level: generator.level, + grid: generator.build_empty_matrix(), + rng: generator.rng, + } + } + pub fn get_rng(&mut self) -> &mut Pcg32 { + &mut self.rng + } + pub fn get_level(&self) -> usize { + self.level + } + pub fn get_cell(&self, pos: (usize, usize)) -> Cell { + self.grid[pos.0][pos.1] + } + + pub fn compute_entities(&mut self, game: &mut Rogue) { + for entity in &mut self.entities { + entity.compute_effects(game); + entity.do_action(game, game.input_action(entity)); + } + } +} diff --git a/src/es03_game/game.rs b/src/es03_game/game.rs new file mode 100644 index 0000000..5c8f56d --- /dev/null +++ b/src/es03_game/game.rs @@ -0,0 +1,53 @@ +use std::fmt::Display; + +use rand::{RngCore, SeedableRng}; +use rand_pcg::Pcg32; +use serde::{Deserialize, Serialize}; + +use super::{config::Config, entities::{Action, Entity}, floor::Floor, generator::Generator}; + +/** + * Struttura del gioco generico che implementa un RogueLike. + */ +#[derive(Deserialize, Serialize)] +pub struct Rogue { + floor: Floor, + rng: Pcg32, + config: Config, +} + +impl Display for Rogue { + fn fmt(&self, _f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + todo!() + } +} + +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 } + } + + pub fn current_floor(&mut self) -> &mut Floor { + &mut self.floor + } + + pub fn build_new_floor(&mut self) { + let level = self.floor.get_level(); + let floor_seed = self.rng.next_u64(); + + let generator = Generator::new(floor_seed, level + 1, &self.config); + self.floor = Floor::new(generator); + } + + pub fn input_action(&self, entity: &Entity) -> Action { + todo!() + } + + pub fn compute_turn(&mut self) { + todo!(); + } +} diff --git a/src/es03_game/generator.rs b/src/es03_game/generator.rs new file mode 100644 index 0000000..8df09bf --- /dev/null +++ b/src/es03_game/generator.rs @@ -0,0 +1,61 @@ +use rand::Rng; +use rand::SeedableRng; +use rand_pcg::Pcg32; + +use super::cell::Cell; + +use super::config::Config; +use super::config::ConfigEffect; + +pub struct Generator<'a> { + pub rng: Pcg32, + pub level: usize, + size: usize, + effects: Vec<&'a ConfigEffect>, + effects_total: usize, + // enemies vec configuration? +} + +impl<'a> Generator<'a> { + pub fn new(floor_seed: u64, floor_level: usize, config: &'a 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(); + + Self { + rng: rand_pcg, + level: floor_level, + size: floor_size, + effects_total: config.effects_total, + effects: effects_list, + } + } + + pub fn build_empty_matrix(&self) -> Vec> { + self.build_matrix_with(Cell::Empty) + } + pub fn build_labyrinth(&mut self) -> Vec> { + todo!() + } + pub fn build_rooms(&mut self) -> Vec> { + todo!() + } + + fn place_staircase(&mut self, grid: Vec>) -> Vec> { + todo!() + } + fn build_matrix_with(&self, cell: Cell) -> Vec> { + vec![vec![cell; self.size]; self.size] + } +} diff --git a/src/lib.rs b/src/lib.rs index 1cdcdf3..20f83b2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,2 +1,3 @@ pub mod es01_anagram; pub mod es02_rational; +pub mod es03_game;