Skip to content

Commit bc1060c

Browse files
authored
Merge pull request #74 from facundoolano/chest_random
Random chest contents
2 parents 89a313c + 574486f commit bc1060c

6 files changed

Lines changed: 124 additions & 39 deletions

File tree

src/character/mod.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,13 @@ impl Character {
203203
_ => Ok(None),
204204
}
205205
}
206+
207+
/// Return the player level rounded to offer items at "pretty levels", e.g.
208+
/// potion[1], sword[5]
209+
pub fn rounded_level(self: &Character) -> i32 {
210+
// allow level 1 or level 5n
211+
std::cmp::max(1, (self.level / 5) * 5)
212+
}
206213
}
207214

208215
#[cfg(test)]

src/game/chest.rs

Lines changed: 66 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use crate::game;
22
use crate::item::equipment::{Shield, Sword};
3-
use crate::item::{equipment::Equipment, Item, Potion};
3+
use crate::item::{equipment::Equipment, Escape, Item, Potion, Remedy};
44
use crate::randomizer::random;
55
use crate::randomizer::Randomizer;
66
use serde::{Deserialize, Serialize};
@@ -20,34 +20,35 @@ pub struct Chest {
2020
impl Chest {
2121
/// Randomly generate a chest at the current location.
2222
pub fn generate(game: &game::Game) -> Option<Self> {
23-
// FIXME improve random generation logic
24-
// FIXME inlcude other items
25-
26-
match random().range(6) {
27-
0 => {
28-
let gold = random().gold_gained(game.player.level * 200);
29-
Some(Self {
30-
items: HashMap::new(),
31-
sword: None,
32-
shield: None,
33-
gold,
34-
})
35-
}
36-
1 => {
37-
let potion = Box::new(Potion::new(game.player.level));
38-
let potions: Vec<Box<dyn Item>> = vec![potion];
39-
40-
let mut items = HashMap::new();
41-
items.insert("potion".to_string(), potions);
42-
43-
Some(Self {
44-
items,
45-
sword: None,
46-
shield: None,
47-
gold: 0,
48-
})
49-
}
50-
_ => None,
23+
// To give the impression of "dynamic" chest contents, each content type
24+
// is randomized separately, and what's found is combined into a single
25+
// chest at the end
26+
let distance = &game.location.distance_from_home();
27+
let gold_chest = random().gold_chest(distance);
28+
let equipment_chest = random().equipment_chest(distance);
29+
let item_chest = random().item_chest(distance);
30+
31+
let mut chest = Self::default();
32+
33+
if gold_chest {
34+
chest.gold = random().gold_gained(game.player.level * 200)
35+
}
36+
37+
if equipment_chest {
38+
let (sword, shield) = random_equipment(game.player.rounded_level());
39+
chest.sword = sword;
40+
chest.shield = shield;
41+
}
42+
43+
if item_chest {
44+
chest.items = random_items(game.player.rounded_level());
45+
}
46+
47+
// Return None instead of an empty chest if none was found
48+
if gold_chest || equipment_chest || item_chest {
49+
Some(chest)
50+
} else {
51+
None
5152
}
5253
}
5354

@@ -125,6 +126,42 @@ impl Chest {
125126
}
126127
}
127128

129+
// TODO consider using weighted random instead of these matches
130+
fn random_equipment(level: i32) -> (Option<Sword>, Option<Shield>) {
131+
match random().range(15) {
132+
n if n < 8 => (Some(Sword::new(level)), None),
133+
n if n < 13 => (None, Some(Shield::new(level))),
134+
14 => (Some(Sword::new(level + 5)), None),
135+
_ => (None, Some(Shield::new(level + 5))),
136+
}
137+
}
138+
139+
fn random_items(level: i32) -> HashMap<String, Vec<Box<dyn Item>>> {
140+
let mut map = HashMap::new();
141+
let potion = || Box::new(Potion::new(level));
142+
143+
let (key, items): (&str, Vec<Box<dyn Item>>) = match random().range(15) {
144+
n if n < 7 => ("potion", vec![potion()]),
145+
n if n < 11 => ("potion", vec![potion(), potion()]),
146+
n if n < 13 => ("potion", vec![potion(), potion(), potion()]),
147+
13 => ("remedy", vec![Box::new(Remedy::new())]),
148+
_ => ("escape", vec![Box::new(Escape::new())]),
149+
};
150+
map.insert(key.to_string(), items);
151+
map
152+
}
153+
154+
impl Default for Chest {
155+
fn default() -> Self {
156+
Self {
157+
gold: 0,
158+
sword: None,
159+
shield: None,
160+
items: HashMap::new(),
161+
}
162+
}
163+
}
164+
128165
#[cfg(test)]
129166
mod tests {
130167
use super::*;

src/game/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,8 @@ impl Game {
135135
self.visit(Location::home()).unwrap_or_default();
136136
}
137137

138+
// TODO consider introducing an item "bag" wrapper over these types of hashmaps
139+
// (same is used in chests and in tests)
138140
pub fn add_item(&mut self, name: &str, item: Box<dyn Item>) {
139141
let entry = self
140142
.inventory

src/item/shop.rs

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ pub fn buy(game: &mut Game, item: &str) -> Result<(), Error> {
3737
/// Build a list of items currently available at the shop
3838
fn available_items(player: &Character) -> Vec<(String, Box<dyn Shoppable>)> {
3939
let mut items = Vec::<(String, Box<dyn Shoppable>)>::new();
40-
let level = available_level(player);
40+
let level = player.rounded_level();
4141

4242
let sword = Sword::new(level);
4343
if sword.is_upgrade_from(&player.sword.as_ref()) {
@@ -61,13 +61,6 @@ fn available_items(player: &Character) -> Vec<(String, Box<dyn Shoppable>)> {
6161
items
6262
}
6363

64-
/// The offered items/equipment have levels e.g. potion[1], sword[5], etc.
65-
/// they become available for purchase only when the player reaches that level
66-
fn available_level(player: &Character) -> i32 {
67-
// allow level 1 or level 5n
68-
std::cmp::max(1, (player.level / 5) * 5)
69-
}
70-
7164
pub trait Shoppable: Display {
7265
fn cost(&self) -> i32;
7366
fn buy(&self, game: &mut Game) -> Result<(), Error> {

src/log.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -297,11 +297,11 @@ fn chest(items: &[String], gold: i32) {
297297
}
298298

299299
fn tombstone(items: &[String], gold: i32) {
300-
format_ls("\u{1FAA6}", items, gold);
300+
format_ls("\u{1FAA6} ", items, gold);
301301
}
302302

303303
fn format_ls(emoji: &str, items: &[String], gold: i32) {
304-
print!("{} ", emoji);
304+
print!("{}", emoji);
305305
if gold > 0 {
306306
print!(" {}", format_gold_plus(gold));
307307
}

src/randomizer.rs

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,10 @@ pub trait Randomizer {
3333
fn stat_increase(&self, increase: i32) -> i32;
3434

3535
fn range(&self, max: i32) -> i32;
36+
37+
fn gold_chest(&self, distance: &location::Distance) -> bool;
38+
fn equipment_chest(&self, distance: &location::Distance) -> bool;
39+
fn item_chest(&self, distance: &location::Distance) -> bool;
3640
}
3741

3842
#[cfg(not(test))]
@@ -127,6 +131,36 @@ impl Randomizer for DefaultRandomizer {
127131
let mut rng = rand::thread_rng();
128132
rng.gen_range(0..max)
129133
}
134+
135+
fn gold_chest(&self, distance: &location::Distance) -> bool {
136+
let mut rng = rand::thread_rng();
137+
138+
match distance {
139+
location::Distance::Near(_) => rng.gen_ratio(5, 20),
140+
location::Distance::Mid(_) => rng.gen_ratio(6, 20),
141+
location::Distance::Far(_) => rng.gen_ratio(3, 20),
142+
}
143+
}
144+
145+
fn equipment_chest(&self, distance: &location::Distance) -> bool {
146+
let mut rng = rand::thread_rng();
147+
148+
match distance {
149+
location::Distance::Near(_) => false,
150+
location::Distance::Mid(_) => rng.gen_ratio(3, 20),
151+
location::Distance::Far(_) => rng.gen_ratio(5, 20),
152+
}
153+
}
154+
155+
fn item_chest(&self, distance: &location::Distance) -> bool {
156+
let mut rng = rand::thread_rng();
157+
158+
match distance {
159+
location::Distance::Near(_) => rng.gen_ratio(3, 20),
160+
location::Distance::Mid(_) => rng.gen_ratio(8, 20),
161+
location::Distance::Far(_) => rng.gen_ratio(14, 20),
162+
}
163+
}
130164
}
131165

132166
fn is_critical() -> bool {
@@ -198,6 +232,18 @@ impl Randomizer for TestRandomizer {
198232
fn range(&self, max: i32) -> i32 {
199233
max
200234
}
235+
236+
fn gold_chest(&self, _distance: &location::Distance) -> bool {
237+
false
238+
}
239+
240+
fn equipment_chest(&self, _distance: &location::Distance) -> bool {
241+
false
242+
}
243+
244+
fn item_chest(&self, _distance: &location::Distance) -> bool {
245+
false
246+
}
201247
}
202248

203249
#[cfg(test)]

0 commit comments

Comments
 (0)