Compare commits
6 Commits
a0635de65a
...
6f4c37728f
Author | SHA1 | Date |
---|---|---|
Joachim Lusiardi | 6f4c37728f | |
Joachim Lusiardi | 5111fec1fa | |
Joachim Lusiardi | 9fb3c83b67 | |
Joachim Lusiardi | 9ae713b6c8 | |
Joachim Lusiardi | 5949b2a2d6 | |
Joachim Lusiardi | 2b8f7eebba |
|
@ -6,6 +6,7 @@ use crate::position::Position;
|
||||||
|
|
||||||
pub trait Artifact {
|
pub trait Artifact {
|
||||||
//! An artifact that can be collected by the player
|
//! An artifact that can be collected by the player
|
||||||
|
/// get the character and color used to draw the artifact into the level
|
||||||
fn get_representation(&self) -> (&str, Color);
|
fn get_representation(&self) -> (&str, Color);
|
||||||
/// get the position of the artifact in the level
|
/// get the position of the artifact in the level
|
||||||
fn get_immutable_position(&self) -> &Position;
|
fn get_immutable_position(&self) -> &Position;
|
||||||
|
@ -15,13 +16,17 @@ pub trait Artifact {
|
||||||
fn was_collected(&self) -> bool;
|
fn was_collected(&self) -> bool;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// An artifact that contains a random amount of gold pieces.
|
||||||
pub struct Chest {
|
pub struct Chest {
|
||||||
/// a chest that contains some gold
|
/// the chests position
|
||||||
position: Position,
|
position: Position,
|
||||||
|
/// the chests value
|
||||||
gold: usize,
|
gold: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Chest {
|
impl Chest {
|
||||||
|
/// create a chest at the given position with a random amount of gold.
|
||||||
|
/// The gold amount depends on the level, the deeper you go, the more you get.
|
||||||
pub fn new(position: Position) -> Self {
|
pub fn new(position: Position) -> Self {
|
||||||
let min_gold = 10 * (position.get_level() + 1);
|
let min_gold = 10 * (position.get_level() + 1);
|
||||||
let max_gold = min_gold + 10 * position.get_level();
|
let max_gold = min_gold + 10 * position.get_level();
|
||||||
|
@ -54,10 +59,13 @@ impl Artifact for Chest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
/// An artifact that gives the player some health on consumption.
|
||||||
pub struct Potion {
|
pub struct Potion {
|
||||||
/// a potion that restores some health
|
/// a potion that restores some health
|
||||||
position: Position,
|
position: Position,
|
||||||
health: usize,
|
health: usize,
|
||||||
|
was_collected: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Potion {
|
impl Potion {
|
||||||
|
@ -67,19 +75,29 @@ impl Potion {
|
||||||
Self {
|
Self {
|
||||||
position,
|
position,
|
||||||
health: rand::thread_rng().gen_range(min_health_gain..=max_health_gain),
|
health: rand::thread_rng().gen_range(min_health_gain..=max_health_gain),
|
||||||
|
was_collected: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_health(&self) -> usize {
|
||||||
|
self.health
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Artifact for Potion {
|
impl Artifact for Potion {
|
||||||
fn get_representation(&self) -> (&str, Color) {
|
fn get_representation(&self) -> (&str, Color) {
|
||||||
("P", Color::Green)
|
("P", Color::Green)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_immutable_position(&self) -> &Position {
|
fn get_immutable_position(&self) -> &Position {
|
||||||
&self.position
|
&self.position
|
||||||
}
|
}
|
||||||
|
|
||||||
fn collect(&mut self, player: &mut Player, messages: &mut Vec<String>) {
|
fn collect(&mut self, player: &mut Player, messages: &mut Vec<String>) {
|
||||||
// only consume potion of the player can gain at least one health point
|
//! called when the player walked on to a potion.
|
||||||
|
//!
|
||||||
|
//! Depending on health status and inventory usage the potion will
|
||||||
|
//! be consumed directly or moved to inventory.
|
||||||
if !player.is_healthy() {
|
if !player.is_healthy() {
|
||||||
let old = player.get_life();
|
let old = player.get_life();
|
||||||
player.change_life(self.health.try_into().unwrap());
|
player.change_life(self.health.try_into().unwrap());
|
||||||
|
@ -89,15 +107,16 @@ impl Artifact for Potion {
|
||||||
format!("picked up potion and gained {} health.", new - old).to_string(),
|
format!("picked up potion and gained {} health.", new - old).to_string(),
|
||||||
);
|
);
|
||||||
self.health = 0;
|
self.health = 0;
|
||||||
|
self.was_collected = true;
|
||||||
|
} else if player.add_to_inventory(self) {
|
||||||
|
messages.insert(0, "move potion to inventory.".to_string());
|
||||||
|
self.was_collected = true;
|
||||||
} else {
|
} else {
|
||||||
messages.insert(
|
messages.insert(0, "inventory is full.".to_string());
|
||||||
0,
|
|
||||||
"not using the potion because you're healthy.".to_string(),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn was_collected(&self) -> bool {
|
fn was_collected(&self) -> bool {
|
||||||
self.health == 0
|
self.was_collected
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,6 +22,7 @@ pub struct Game {
|
||||||
player: Player,
|
player: Player,
|
||||||
/// the levels of the game
|
/// the levels of the game
|
||||||
levels: Vec<Level>,
|
levels: Vec<Level>,
|
||||||
|
/// messages that are displayed in the ui
|
||||||
pub messages: Vec<String>,
|
pub messages: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -130,7 +131,7 @@ impl Game {
|
||||||
let (next_level, x, y) = self.next_start();
|
let (next_level, x, y) = self.next_start();
|
||||||
player_level = next_level;
|
player_level = next_level;
|
||||||
self.messages
|
self.messages
|
||||||
.insert(0, format!("you climb down to level {}.", next_level));
|
.insert(0, format!("you climb down to level {}.", next_level + 1));
|
||||||
self.get_mutable_player()
|
self.get_mutable_player()
|
||||||
.get_position()
|
.get_position()
|
||||||
.set(next_level, x, y);
|
.set(next_level, x, y);
|
||||||
|
@ -140,7 +141,7 @@ impl Game {
|
||||||
let (next_level, x, y) = self.prev_end();
|
let (next_level, x, y) = self.prev_end();
|
||||||
player_level = next_level;
|
player_level = next_level;
|
||||||
self.messages
|
self.messages
|
||||||
.insert(0, format!("you climb up to level {}.", next_level));
|
.insert(0, format!("you climb up to level {}.", next_level + 1));
|
||||||
self.get_mutable_player()
|
self.get_mutable_player()
|
||||||
.get_position()
|
.get_position()
|
||||||
.set(next_level, x, y);
|
.set(next_level, x, y);
|
||||||
|
|
|
@ -6,6 +6,10 @@ use rand::Rng;
|
||||||
use crate::artifacts::Artifact;
|
use crate::artifacts::Artifact;
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
use crate::artifacts::{Chest, Potion};
|
use crate::artifacts::{Chest, Potion};
|
||||||
|
use crate::level_generator::ROOMS_HORIZONTAL;
|
||||||
|
use crate::level_generator::ROOMS_VERTICAL;
|
||||||
|
use crate::level_generator::ROOM_HEIGHT;
|
||||||
|
use crate::level_generator::ROOM_WIDTH;
|
||||||
use crate::monster::Monster;
|
use crate::monster::Monster;
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
use crate::monster::{Orc, Rat};
|
use crate::monster::{Orc, Rat};
|
||||||
|
@ -13,8 +17,8 @@ use crate::player::Player;
|
||||||
use crate::position::HasPosition;
|
use crate::position::HasPosition;
|
||||||
use crate::position::Position;
|
use crate::position::Position;
|
||||||
|
|
||||||
pub const LEVEL_WIDTH: usize = 50;
|
pub const LEVEL_WIDTH: usize = 1 + ROOMS_VERTICAL * ROOM_WIDTH;
|
||||||
pub const LEVEL_HEIGHT: usize = 25;
|
pub const LEVEL_HEIGHT: usize = 1 + ROOMS_HORIZONTAL * ROOM_HEIGHT;
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||||
pub enum StructureElement {
|
pub enum StructureElement {
|
||||||
|
|
|
@ -14,11 +14,11 @@ use crate::level::{Level, StructureElement};
|
||||||
use crate::monster::{Monster, Orc, Rat, Snake};
|
use crate::monster::{Monster, Orc, Rat, Snake};
|
||||||
use crate::position::Position;
|
use crate::position::Position;
|
||||||
|
|
||||||
const ROOMS_VERTICAL: usize = 7;
|
pub const ROOMS_VERTICAL: usize = 9;
|
||||||
const ROOMS_HORIZONTAL: usize = 4;
|
pub const ROOMS_HORIZONTAL: usize = 7;
|
||||||
|
|
||||||
const ROOM_WIDTH: usize = 7;
|
pub const ROOM_WIDTH: usize = 7;
|
||||||
const ROOM_HEIGHT: usize = 6;
|
pub const ROOM_HEIGHT: usize = 6;
|
||||||
|
|
||||||
#[derive(PartialEq, Copy, Clone)]
|
#[derive(PartialEq, Copy, Clone)]
|
||||||
enum RoomType {
|
enum RoomType {
|
||||||
|
|
55
src/main.rs
55
src/main.rs
|
@ -33,6 +33,9 @@ mod position;
|
||||||
/// length of a game frame in ms
|
/// length of a game frame in ms
|
||||||
pub const FRAME_LENGTH: u64 = 100;
|
pub const FRAME_LENGTH: u64 = 100;
|
||||||
|
|
||||||
|
pub const MIN_WIDTH: u16 = 120;
|
||||||
|
pub const MIN_HEIGHT: u16 = 30;
|
||||||
|
|
||||||
//
|
//
|
||||||
fn main() -> Result<()> {
|
fn main() -> Result<()> {
|
||||||
let mut game = Game::new(Player::new(realname().as_str(), 30));
|
let mut game = Game::new(Player::new(realname().as_str(), 30));
|
||||||
|
@ -54,27 +57,30 @@ fn main() -> Result<()> {
|
||||||
|
|
||||||
// don't draw stuff except an info box if the terminal is too small (less than 80x25)
|
// don't draw stuff except an info box if the terminal is too small (less than 80x25)
|
||||||
// to prevent the read drawing code from crashing the game.
|
// to prevent the read drawing code from crashing the game.
|
||||||
if area.width < 80 || area.height < 25 {
|
if area.width < MIN_WIDTH || area.height < MIN_HEIGHT {
|
||||||
let block = Block::default()
|
let block = Block::default()
|
||||||
.title("Info")
|
.title("Info")
|
||||||
.borders(Borders::ALL)
|
.borders(Borders::ALL)
|
||||||
.border_style(Style::default().fg(Color::White))
|
.border_style(Style::default().fg(Color::White))
|
||||||
.border_type(BorderType::Rounded)
|
.border_type(BorderType::Rounded)
|
||||||
.style(Style::default().bg(Color::Black));
|
.style(Style::default().bg(Color::Black));
|
||||||
let paragraph = Paragraph::new("Terminal needs to be at leas 80x25!")
|
let paragraph = Paragraph::new(format!(
|
||||||
|
"Terminal needs to be at leas {}x{}!",
|
||||||
|
MIN_WIDTH, MIN_HEIGHT
|
||||||
|
))
|
||||||
.block(block)
|
.block(block)
|
||||||
.wrap(Wrap { trim: true });
|
.wrap(Wrap { trim: true });
|
||||||
frame.render_widget(paragraph, area);
|
frame.render_widget(paragraph, area);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if area.width > 80 {
|
if area.width > MIN_WIDTH {
|
||||||
area.x = (area.width - 80) / 2;
|
area.x = (area.width - MIN_WIDTH) / 2;
|
||||||
area.width = 80;
|
area.width = MIN_WIDTH;
|
||||||
}
|
}
|
||||||
if area.height > 25 {
|
if area.height > MIN_HEIGHT {
|
||||||
area.y = (area.height - 25) / 2;
|
area.y = (area.height - level::LEVEL_HEIGHT as u16) / 2;
|
||||||
area.height = 25;
|
area.height = level::LEVEL_HEIGHT as u16;
|
||||||
}
|
}
|
||||||
|
|
||||||
let map_area = Rect {
|
let map_area = Rect {
|
||||||
|
@ -86,10 +92,10 @@ fn main() -> Result<()> {
|
||||||
frame.render_stateful_widget(LevelWidget {}, map_area, &mut game);
|
frame.render_stateful_widget(LevelWidget {}, map_area, &mut game);
|
||||||
|
|
||||||
let stats_area = Rect {
|
let stats_area = Rect {
|
||||||
x: area.x + 50,
|
x: area.x + map_area.width,
|
||||||
y: area.y,
|
y: area.y,
|
||||||
width: 30,
|
width: MIN_WIDTH - map_area.width,
|
||||||
height: 15,
|
height: map_area.height / 2 + 1,
|
||||||
};
|
};
|
||||||
let block = Block::default()
|
let block = Block::default()
|
||||||
.title(
|
.title(
|
||||||
|
@ -103,22 +109,24 @@ fn main() -> Result<()> {
|
||||||
.style(Style::default().bg(Color::Blue));
|
.style(Style::default().bg(Color::Blue));
|
||||||
frame.render_widget(
|
frame.render_widget(
|
||||||
Paragraph::new(format!(
|
Paragraph::new(format!(
|
||||||
"Health: {}/{}\nExp: {}\nGold: {}\nLevel: {}",
|
"Health: {}/{}\nExp: {}\nGold: {}\nLevel: {}\nInventory: {}/{}",
|
||||||
game.get_player().get_life(),
|
game.get_player().get_life(),
|
||||||
game.get_player().get_max_life(),
|
game.get_player().get_max_life(),
|
||||||
game.get_player().get_experience(),
|
game.get_player().get_experience(),
|
||||||
game.get_player().get_gold(),
|
game.get_player().get_gold(),
|
||||||
game.get_player().get_immutable_position().get_level() + 1
|
game.get_player().get_immutable_position().get_level() + 1,
|
||||||
|
game.get_player().inventory_size().0,
|
||||||
|
game.get_player().inventory_size().1,
|
||||||
))
|
))
|
||||||
.block(block)
|
.block(block)
|
||||||
.wrap(Wrap { trim: true }),
|
.wrap(Wrap { trim: true }),
|
||||||
stats_area,
|
stats_area,
|
||||||
);
|
);
|
||||||
let messages_area = Rect {
|
let messages_area = Rect {
|
||||||
x: area.x + 50,
|
x: area.x + map_area.width,
|
||||||
y: area.y + 15,
|
y: area.y + map_area.height / 2 + 1,
|
||||||
width: 30,
|
width: MIN_WIDTH - map_area.width,
|
||||||
height: 10,
|
height: map_area.height / 2,
|
||||||
};
|
};
|
||||||
// Display the latest messages from the game to the user
|
// Display the latest messages from the game to the user
|
||||||
let block = Block::default()
|
let block = Block::default()
|
||||||
|
@ -155,6 +163,19 @@ fn main() -> Result<()> {
|
||||||
.to_string(),
|
.to_string(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
KeyCode::Char('p') => {
|
||||||
|
let gained_health = game.get_mutable_player().consume_inventory();
|
||||||
|
if gained_health > 0 {
|
||||||
|
game.messages.insert(
|
||||||
|
0,
|
||||||
|
format!(
|
||||||
|
"used a potion from inventory and gained {} health.",
|
||||||
|
gained_health
|
||||||
|
)
|
||||||
|
.to_string(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
KeyCode::Char('q') => {
|
KeyCode::Char('q') => {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
use rand::Rng;
|
use rand::Rng;
|
||||||
use std::cmp::{max, min};
|
use std::cmp::{max, min};
|
||||||
|
|
||||||
|
use crate::artifacts::Potion;
|
||||||
use crate::position::{HasPosition, Position};
|
use crate::position::{HasPosition, Position};
|
||||||
|
|
||||||
pub struct Player {
|
pub struct Player {
|
||||||
|
@ -10,6 +11,8 @@ pub struct Player {
|
||||||
max_life: i16,
|
max_life: i16,
|
||||||
gold: usize,
|
gold: usize,
|
||||||
experience: usize,
|
experience: usize,
|
||||||
|
inventory: Vec<Potion>,
|
||||||
|
inventory_slots: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Player {
|
impl Player {
|
||||||
|
@ -21,6 +24,8 @@ impl Player {
|
||||||
max_life,
|
max_life,
|
||||||
gold: 0,
|
gold: 0,
|
||||||
experience: 0,
|
experience: 0,
|
||||||
|
inventory: vec![],
|
||||||
|
inventory_slots: 2,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn get_name(&self) -> String {
|
pub fn get_name(&self) -> String {
|
||||||
|
@ -65,6 +70,30 @@ impl Player {
|
||||||
pub fn damage(&self) -> usize {
|
pub fn damage(&self) -> usize {
|
||||||
rand::thread_rng().gen_range(1..4)
|
rand::thread_rng().gen_range(1..4)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn add_to_inventory(&mut self, potion: &Potion) -> bool {
|
||||||
|
if self.inventory.len() < self.inventory_slots {
|
||||||
|
self.inventory.push(*potion);
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn inventory_size(&self) -> (usize, usize) {
|
||||||
|
(self.inventory.len(), self.inventory_slots)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn consume_inventory(&mut self) -> usize {
|
||||||
|
if self.is_healthy() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if let Some(potion) = self.inventory.pop() {
|
||||||
|
self.change_life(potion.get_health() as i16);
|
||||||
|
return potion.get_health();
|
||||||
|
}
|
||||||
|
0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl HasPosition for Player {
|
impl HasPosition for Player {
|
||||||
|
@ -85,6 +114,7 @@ fn test_get_name() {
|
||||||
max_life: 10,
|
max_life: 10,
|
||||||
gold: 0,
|
gold: 0,
|
||||||
experience: 0,
|
experience: 0,
|
||||||
|
inventory: vec![],
|
||||||
};
|
};
|
||||||
assert_eq!(p.get_name(), "Teddy Tester");
|
assert_eq!(p.get_name(), "Teddy Tester");
|
||||||
}
|
}
|
||||||
|
@ -108,6 +138,7 @@ fn test_change_life() {
|
||||||
max_life: 10,
|
max_life: 10,
|
||||||
gold: 0,
|
gold: 0,
|
||||||
experience: 0,
|
experience: 0,
|
||||||
|
inventory: vec![],
|
||||||
};
|
};
|
||||||
assert_eq!(p.get_life(), 5);
|
assert_eq!(p.get_life(), 5);
|
||||||
p.change_life(-2);
|
p.change_life(-2);
|
||||||
|
@ -141,6 +172,7 @@ fn test_max_life() {
|
||||||
max_life: 10,
|
max_life: 10,
|
||||||
gold: 0,
|
gold: 0,
|
||||||
experience: 0,
|
experience: 0,
|
||||||
|
inventory: vec![],
|
||||||
};
|
};
|
||||||
assert_eq!(p.get_max_life(), 10);
|
assert_eq!(p.get_max_life(), 10);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue