Skip to content

Commit

Permalink
use tools:weighted_rng function for choosing new store stock
Browse files Browse the repository at this point in the history
  • Loading branch information
cdsupina committed Jun 26, 2021
1 parent fdc940b commit 24f5296
Show file tree
Hide file tree
Showing 5 changed files with 88 additions and 55 deletions.
2 changes: 1 addition & 1 deletion thetawave_game/src/states/main_game.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ use thetawave_lib::{
systems::ModifiersSystem,
systems::{MobBehaviorSystem, MobDestroyedSystem},
},
store::systems::StoreSystem,
store::StoreSystem,
visual::resources::{SpriteSheetsConfig, SpriteSheetsResource},
visual::{
entities::{
Expand Down
7 changes: 5 additions & 2 deletions thetawave_lib/src/store/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
//! `thetawave_lib` store module
mod store_resource;
pub mod systems;
mod store_system;

pub use self::store_resource::{StoreConfig, StoreResource};
pub use self::{
store_resource::{StoreConfig, StoreResource},
store_system::StoreSystem,
};
129 changes: 82 additions & 47 deletions thetawave_lib/src/store/store_resource.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,42 +2,54 @@ use crate::{
constants::{ARENA_MAX_Y, ITEM_SPAWN_Y_OFFSET},
player::components::PlayerComponent,
spawnable::resources::{ConsumablesResource, ItemsResource, SpawnableType},
tools::Timer,
tools::{weighted_rng, Timer},
visual::resources::SpriteSheetsResource,
};
use amethyst::prelude::Builder;
use amethyst::{
core::{math::Vector3, transform::Transform},
ecs::prelude::{Entities, Entity, LazyUpdate, ReadExpect},
prelude::Builder,
renderer::SpriteRender,
};
use rand::{thread_rng, Rng};
use serde::{Deserialize, Serialize};
use std::convert::From;

/// Spawnables paired with probabilities of appearing
pub type StockProbabilities = Vec<(SpawnableType, f32)>;

/// Store configuration parameters stored in config file
#[derive(Serialize, Deserialize)]
pub struct StoreConfig {
/// Period of time between refreshing the store
restock_period: f32,
/// Items and consumables stocked in the store
stock: StockProbabilities,
}

/// Resource for managing inventory and allowing players to purchase items and consumables
pub struct StoreResource {
/// Items and consumables availabe for purchase
pub inventory: Vec<Option<SpawnableType>>,
/// Timer for managing restocking the store
pub timer: Timer,
/// Items and consumables stocked in the store
stock: StockProbabilities,
/// Location coordinates of inventory icons
// TODO: store positions of icons in config file
icon_positions: Vec<Vector3<f32>>,
/// Sprite entities representing the inventory items
icon_entities: Vec<Option<Entity>>,
}

impl From<StoreConfig> for StoreResource {
/// Converts StoreConfig to a StoreResource
fn from(config: StoreConfig) -> Self {
StoreResource::new(config.restock_period, config.stock)
}
}

impl StoreResource {
/// Create a new StoreResource from a restock period and stock of consumables and items
pub fn new(restock_period: f32, stock: StockProbabilities) -> Self {
StoreResource {
inventory: vec![None, None, None],
Expand All @@ -52,6 +64,7 @@ impl StoreResource {
}
}

/// Remove icon entities
fn destroy_icon(&mut self, index: usize, entities: &Entities) {
if let Some(icon_entity) = self.icon_entities[index] {
entities
Expand All @@ -61,6 +74,7 @@ impl StoreResource {
}
}

/// Create icon entity for item/consumable in inventory slot
fn create_icon(
&mut self,
index: usize,
Expand All @@ -71,8 +85,10 @@ impl StoreResource {
lazy_update: &ReadExpect<LazyUpdate>,
) {
if let Some(spawnable_type) = self.inventory[index].clone() {
// remove the existing icon
self.destroy_icon(index, entities);

// create the SpriteRender component
let sprite_render = SpriteRender {
sprite_sheet: spritesheets_resource.spritesheets
[&spawnable_type.get_spritesheet_name()].clone(),
Expand All @@ -83,23 +99,27 @@ impl StoreResource {
SpawnableType::Item(item_type) => {
items_resource.item_entities[&item_type].sprite_render_data.initial_index
},
_ => {panic!("Attempted to set icon to invalid Spawnable. Icons must be sent to items or consumables.")}
_ => {panic!("Attempted to set icon to invalid Spawnable. Icons must be set to items or consumables.")}
},
};

// create the Transform component
let mut transform = Transform::default();
transform.set_translation(self.icon_positions[index].into());

// create the store icon entity
let icon_entity = lazy_update
.create_entity(entities)
.with(sprite_render)
.with(transform)
.build();

// store the entity in the icon_entities vector
self.icon_entities[index] = Some(icon_entity);
}
}

/// Update the store's timer and refresh stock
pub fn update(
&mut self,
delta_time: f32,
Expand All @@ -108,20 +128,20 @@ impl StoreResource {
consumables_resource: &ReadExpect<ConsumablesResource>,
entities: &Entities,
lazy_update: &ReadExpect<LazyUpdate>,
) -> bool {
) {
if self.timer.update(delta_time) {
// choose stock if timer resets
self.choose_stock(
spritesheets_resource,
items_resource,
consumables_resource,
entities,
lazy_update,
);
return true;
}
false
}

/// Choose new inventory using StockProbabilities
fn choose_stock(
&mut self,
spritesheets_resource: &ReadExpect<SpriteSheetsResource>,
Expand All @@ -133,42 +153,39 @@ impl StoreResource {
self.inventory = vec![None, None, None];
let mut choose_pool = self.stock.clone();

// choose 3 items
// choose item for each slot
for i in 0..3 {
let total_probs = choose_pool.iter().fold(0.0, |sum, item| sum + item.1);

// choose an item
let pos = thread_rng().gen::<f32>() * total_probs;
let mut sum = 0.0;

for (entity_type, value) in choose_pool.clone() {
sum += value;
if sum > pos {
//let item_to_add = &items_resource.item_entities[&item_type];
choose_pool.retain(|element| element != &(entity_type.clone(), value)); // remove chosen item from cloned choose pool

self.inventory[i] = Some(entity_type.clone());
self.create_icon(
i,
spritesheets_resource,
items_resource,
consumables_resource,
entities,
lazy_update,
);

let entity_index = self
.stock
.iter()
.position(|element| element == &(entity_type.clone(), value))
.unwrap();

if let SpawnableType::Item(_) = entity_type {
self.stock[entity_index].1 /= 2.0; // divide probability of item appearing in store by 2
}
// isolate vector of probabilities
let probs = choose_pool
.iter()
.map(|choose_pool| choose_pool.1)
.collect();

break;
}
// choose an index with weighted rng
let chosen_index = weighted_rng(probs);

// get spawnable type at index
let chosen_stock_type = choose_pool[chosen_index].0.clone();

// remove spawnable from temporary pool
choose_pool.remove(chosen_index);

// set inventory slot to chosen type
self.inventory[i] = Some(chosen_stock_type.clone());

// create icon for chosen stock
self.create_icon(
i,
spritesheets_resource,
items_resource,
consumables_resource,
entities,
lazy_update,
);

// divide probability of item appearing in store by 2
if let SpawnableType::Item(_) = chosen_stock_type {
self.stock[chosen_index].1 /= 2.0;
}
}
}
Expand All @@ -185,13 +202,16 @@ impl StoreResource {
lazy_update: &ReadExpect<LazyUpdate>,
) -> bool {
if let Some(entity_type) = self.inventory[inventory_index].clone() {
self.destroy_icon(inventory_index, entities);
// match item or consumable
match &entity_type {
SpawnableType::Item(item_type) => {
let item_data = items_resource.item_entities[&item_type].clone();

// check if player has enough money
if player.money >= item_data.item_component.price {
player.money -= item_data.item_component.price;

// spawn item at top of arena above player
let mut spawn_transform = Transform::default();
spawn_transform.set_translation_xyz(
transform.translation().x,
Expand All @@ -208,23 +228,33 @@ impl StoreResource {
lazy_update,
);

for (i, e_type) in self.stock.iter().enumerate() {
if e_type.0 == entity_type {
self.stock[i].1 = 0.0; //set probability of appearing again to 0
// set probability of item being chosen again to 0
// TODO: address case where multiple items with different probabilities are in the pool
for (i, stock_probs) in self.stock.iter().enumerate() {
if stock_probs.0 == entity_type {
self.stock[i].1 = 0.0;
break;
}
}

self.inventory[inventory_index] = None; //change item slot data to None
// set store inventory slot to None
self.inventory[inventory_index] = None;

// destroy store icon
self.destroy_icon(inventory_index, entities);

return true;
}
}
SpawnableType::Consumable(consumable_type) => {
let consumable_data =
consumables_resource.consumable_entities[&consumable_type].clone();

// check if player has enough money
if player.money >= consumable_data.consumable_component.price {
player.money -= consumable_data.consumable_component.price;

// spawn item at top of arena above player
let mut spawn_transform = Transform::default();
spawn_transform.set_translation_xyz(
transform.translation().x,
Expand All @@ -241,7 +271,12 @@ impl StoreResource {
lazy_update,
);

self.inventory[inventory_index] = None; //change item slot data to None
// set store inventory slot to None
self.inventory[inventory_index] = None;

// destroy existing store icon
self.destroy_icon(inventory_index, entities);

return true;
}
}
Expand Down
File renamed without changes.
5 changes: 0 additions & 5 deletions thetawave_lib/src/store/systems/mod.rs

This file was deleted.

0 comments on commit 24f5296

Please sign in to comment.