diff --git a/src/artifacts.rs b/src/artifacts.rs index 3d5fce3..16b1510 100644 --- a/src/artifacts.rs +++ b/src/artifacts.rs @@ -9,7 +9,7 @@ pub trait Artifact { /// get the position of the artifact in the level fn get_immutable_position(&self) -> &Position; /// call to apply the effects of the artifact to the player - fn collect(&mut self, player: &mut Player); + fn collect(&mut self, player: &mut Player, messages: &mut Vec); /// returns if the artifact was collected and can be removed from the level fn was_collected(&self) -> bool; } @@ -37,8 +37,9 @@ impl Artifact for Chest { } fn get_immutable_position(&self) -> &Position { &self.position } - fn collect(&mut self, player: &mut Player) { + fn collect(&mut self, player: &mut Player, messages: &mut Vec) { player.retrieve_gold(self.gold); + messages.insert(0, format!("opened chest and collected {} gold.", self.gold).to_string()); self.gold = 0; } @@ -67,11 +68,16 @@ impl Artifact for Potion { ("P", Color::Green) } fn get_immutable_position(&self) -> &Position { &self.position } - fn collect(&mut self, player: &mut Player) { + fn collect(&mut self, player: &mut Player, messages: &mut Vec) { // only consume potion of the player can gain at least one health point if !player.is_healthy() { + let old = player.get_life(); player.change_life(self.health.try_into().unwrap()); + let new = player.get_life(); + messages.insert(0, format!("picked up potion and gained {} hp.", new - old).to_string()); self.health = 0; + } else { + messages.insert(0, "not using the potion because you're healthy.".to_string()); } } diff --git a/src/game.rs b/src/game.rs index e755617..87a8ec2 100644 --- a/src/game.rs +++ b/src/game.rs @@ -23,6 +23,7 @@ pub struct Game { player: Player, /// the levels of the game levels: Vec, + pub messages: Vec, } impl Game { @@ -34,6 +35,7 @@ impl Game { let mut g = Game { player: p, levels: v, + messages: Vec::with_capacity(10), }; let start = { g.get_level(0).start @@ -125,12 +127,14 @@ impl Game { (dx, dy) = (0, 0); let (next_level, x, y) = self.next_start(); player_level = next_level; + self.messages.insert(0, format!("you climb down to level {}.", next_level)); self.get_mutable_player().get_position().set(next_level, x, y); } StructureElement::StairUp => { (dx, dy) = (0, 0); let (next_level, x, y) = self.prev_end(); player_level = next_level; + self.messages.insert(0, format!("you climb up to level {}.", next_level)); self.get_mutable_player().get_position().set(next_level, x, y); } _ => {} @@ -155,6 +159,8 @@ impl Game { // TODO fight the monster self.player.change_life(-1); m.decrease_life(1); + self.messages.insert(0, format!("{} hits you.", m.get_name()).to_string()); + self.messages.insert(0, format!("you hit {}.", m.get_name()).to_string()); // monster died, player gains experience if m.is_dead() { self.player.gain_experience(m.get_experience_gain()); @@ -172,7 +178,7 @@ impl Game { match a { None => {} Some(a) => { - a.collect(&mut self.player); + a.collect(&mut self.player, &mut self.messages); } } } @@ -181,7 +187,7 @@ impl Game { let player_pos = &self.player.get_immutable_position(); let player_level = player_pos.get_level(); let level = &mut self.levels[player_level]; - level.update(ticks, &mut self.player); + level.update(ticks, &mut self.player, &mut self.messages); } } diff --git a/src/level.rs b/src/level.rs index eb07dcd..2b9251a 100644 --- a/src/level.rs +++ b/src/level.rs @@ -112,7 +112,7 @@ impl Level { } } } - pub fn update(&mut self, ticks: u128, player: &mut Player) { + pub fn update(&mut self, ticks: u128, player: &mut Player, messages: &mut Vec) { for (index, a) in &mut self.artifacts.iter().enumerate() { if a.was_collected() { self.artifacts.remove(index); @@ -140,6 +140,8 @@ impl Level { if player.get_immutable_position().get_x() == new_x && player.get_immutable_position().get_y() == new_y { self.monsters[index].decrease_life(1); player.change_life(-1); + messages.insert(0, format!("{} hits you.", self.monsters[index].get_name()).to_string()); + messages.insert(0, format!("you hit {}.", self.monsters[index].get_name()).to_string()); // if the attack did not kill the opponent, back down if !player.is_dead() { self.monsters[index].get_position().change(-dx, -dy); diff --git a/src/main.rs b/src/main.rs index 71d0a95..dc6cc17 100644 --- a/src/main.rs +++ b/src/main.rs @@ -18,7 +18,6 @@ use whoami::realname; use crate::game::{Game, GameState}; use crate::level_widget::LevelWidget; -// use crate::level_widget::LevelWidget; use crate::player::Player; use crate::position::HasPosition; @@ -87,20 +86,48 @@ fn main() -> Result<()> { x: area.x + 50, y: area.y, width: 30, - height: 25, + height: 15, }; + let block = Block::default() + .title( + Title::from( + format!(" {} ", game.get_player().get_name())) + .alignment(Alignment::Center) + .position(Position::Top) + ) + .borders(Borders::TOP) + .border_style(Style::default().fg(Color::White)) + .border_type(BorderType::Rounded) + .style(Style::default().bg(Color::Blue)); frame.render_widget( - Paragraph::new(format!("{}\nHealth: {}/{}\nExp: {}\nGold: {}\nLevel: {}", - game.get_player().get_name(), + Paragraph::new(format!("Health: {}/{}\nExp: {}\nGold: {}\nLevel: {}", game.get_player().get_life(), game.get_player().get_max_life(), game.get_player().get_experience(), game.get_player().get_gold(), game.get_player().get_immutable_position().get_level())) - .white() - .on_blue(), + .block(block).wrap(Wrap { trim: true }), stats_area, ); + let messages_area = Rect { + x: area.x + 50, + y: area.y + 15, + width: 30, + height: 10, + }; + // Display the latest messages from the game to the user + let block = Block::default() + .title(Title::from(" messages ").alignment(Alignment::Center).position(Position::Top)) + .borders(Borders::TOP) + .border_style(Style::default().fg(Color::White)) + .border_type(BorderType::Rounded) + .style(Style::default().bg(Color::Blue)); + + let paragraph1 = if game.messages.is_empty() { "".to_string() } else { format!("> {}", game.messages.join("\n> ")) }; + frame.render_widget( + Paragraph::new(paragraph1).block(block).wrap(Wrap { trim: true }), + messages_area, + ); })?; if event::poll(std::time::Duration::from_millis(FRAME_LENGTH))? { if let event::Event::Key(key) = event::read()? { @@ -140,7 +167,7 @@ fn main() -> Result<()> { area.width = 40; area.height = 20; let block = Block::default() - .title("Game ended") + .title(Title::from(" Game ended ").alignment(Alignment::Center).position(Position::Top)) .title(Title::from("Press `q` to quit!").position(Position::Bottom)) .borders(Borders::ALL) .border_style(Style::default().fg(Color::White)) diff --git a/src/monster.rs b/src/monster.rs index 7ab2120..633a6f4 100644 --- a/src/monster.rs +++ b/src/monster.rs @@ -3,6 +3,7 @@ use ratatui::prelude::Color; use crate::position::{HasPosition, Position}; pub trait Monster: HasPosition { + fn get_name(&self) -> &str; fn is_dead(&self) -> bool; fn get_representation(&self) -> (&str, Color); fn decrease_life(&mut self, by: usize); @@ -16,6 +17,7 @@ pub trait Monster: HasPosition { macro_rules! default_monster { ($($t:ty),+ $(,)?) => ($( impl Monster for $t { + fn get_name(&self) -> &str { &self.name } fn is_dead(&self) -> bool { self.life <= 0 } fn get_experience_gain(&self) -> usize { self.experience_gain } fn get_representation(&self) -> (&str, Color) { (&self.symbol, self.color) } @@ -39,6 +41,7 @@ macro_rules! default_monster { } pub struct Rat { + name: String, life: usize, position: Position, symbol: String, @@ -50,6 +53,7 @@ pub struct Rat { impl Rat { pub fn new_with_position(position: Position) -> Self { Self { + name: "rat".to_string(), life: 2, position, symbol: String::from("R"), @@ -62,6 +66,7 @@ impl Rat { default_monster!(Rat); pub struct Orc { + name: String, life: usize, position: Position, symbol: String, @@ -73,6 +78,7 @@ pub struct Orc { impl Orc { pub fn new_with_position(position: Position) -> Self { Self { + name: "orc".to_string(), life: 4, position, symbol: String::from("O"), @@ -88,7 +94,7 @@ default_monster!(Orc); #[test] fn monsters_can_move() { - let mut m = Rat::new_with_position(Position::new(0,0,0)); + let mut m = Rat::new_with_position(Position::new(0, 0, 0)); assert_eq!(m.get_position(), &Position::new(0, 0, 0)); m.get_position().change(1, 2); assert_eq!(m.get_position(), &Position::new(0, 1, 2)); @@ -101,7 +107,7 @@ fn monsters_can_move() { #[test] fn monsters_can_die() { - let mut m = Rat::new_with_position(Position::new(0,0,0)); + let mut m = Rat::new_with_position(Position::new(0, 0, 0)); assert_eq!(m.get_life(), 2); assert_eq!(m.is_dead(), false); m.decrease_life(1);