Skip to content

Commit

Permalink
feat:
Browse files Browse the repository at this point in the history
* add shield in random gen;
* lower minimum size of dice to 1 (to allow fixed numbers aka 100d1 => 100;
* add safe guarantees around n of forged items range gen, now it is formally proved that it will not panic and isolated in a method to reduce redundancy.
  • Loading branch information
RakuJa committed Jul 7, 2024
1 parent 55d5fb8 commit 9406c09
Show file tree
Hide file tree
Showing 6 changed files with 81 additions and 14 deletions.
9 changes: 8 additions & 1 deletion src/db/data_providers/raw_query_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,16 @@ pub fn prepare_filtered_get_items(shop_filter_query: &ShopFilterQuery) -> String
max_level,
&supported_pf_versions,
);
let shield_query = prepare_item_subquery(
&ItemTypeEnum::Shield,
shop_filter_query.n_of_shields,
min_level,
max_level,
&supported_pf_versions,
);
let query = format!(
"SELECT * FROM ITEM_TABLE WHERE id IN ( {equipment_query} ) OR id IN ({consumable_query} )
OR id IN ({weapon_query} ) OR id IN ({armor_query} )"
OR id IN ({weapon_query} ) OR id IN ({armor_query} ) OR id IN ({shield_query} )"
);
debug!("{}", query);
query
Expand Down
5 changes: 5 additions & 0 deletions src/db/data_providers/shop_fetcher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,10 @@ pub async fn fetch_items_with_filters(
.iter()
.filter(|x| x.item_type == ItemTypeEnum::Armor)
.collect();
let shields: Vec<&Item> = items
.iter()
.filter(|x| x.item_type == ItemTypeEnum::Shield)
.collect();
let consumables: Vec<&Item> = items
.iter()
.filter(|x| x.item_type == ItemTypeEnum::Consumable)
Expand All @@ -281,6 +285,7 @@ pub async fn fetch_items_with_filters(
item_vec.extend(fill_item_vec_to_len(&consumables, filters.n_of_consumables));
item_vec.extend(fill_item_vec_to_len(&weapons, filters.n_of_weapons));
item_vec.extend(fill_item_vec_to_len(&armors, filters.n_of_armors));
item_vec.extend(fill_item_vec_to_len(&shields, filters.n_of_shields));

Ok(item_vec)
}
Expand Down
14 changes: 12 additions & 2 deletions src/models/routers_validator_structs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,15 +102,25 @@ impl Default for PaginatedRequest {
pub struct Dice {
#[validate(range(min = 1, max = 255))]
pub n_of_dices: u8,
#[validate(range(min = 2, max = 255))]
// 1 needs to be an option, to allow 100d1 => 100
#[validate(range(min = 1, max = 255))]
pub dice_size: u8,
}

impl Dice {
/// Dice roll will roll n dices with each roll in the range of 1<=result<=dice_size.
/// It returns the sum of n_of_dices rolls.
/// IT SHOULD NEVER BE <1, OTHERWISE WE BREAK THE CONTRACT OF THE METHOD.
pub fn roll(&self) -> i64 {
let mut roll_result = 0;
for _ in 0..self.n_of_dices {
roll_result += rand::thread_rng().gen_range(1..=self.dice_size) as i64
// gen_range panics if n<2 (1..1), panic!
// so we directly return 1 if that's the case
roll_result += if self.dice_size > 1 {
rand::thread_rng().gen_range(1..=self.dice_size) as i64
} else {
1
}
}
roll_result
}
Expand Down
1 change: 1 addition & 0 deletions src/models/shop_structs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ pub struct ShopFilterQuery {
pub n_of_consumables: i64,
pub n_of_weapons: i64,
pub n_of_armors: i64,
pub n_of_shields: i64,
pub pathfinder_version: PathfinderVersionEnum,
}

Expand Down
2 changes: 2 additions & 0 deletions src/routes/shop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use crate::models::item::armor_struct::ArmorData;
use crate::models::item::item_metadata::type_enum::ItemTypeEnum;
use crate::models::item::item_metadata::type_enum::WeaponTypeEnum;
use crate::models::item::item_struct::Item;
use crate::models::item::shield_struct::ShieldData;
use crate::models::item::weapon_struct::WeaponData;
use crate::models::response_data::ResponseItem;
use crate::models::routers_validator_structs::ItemFieldFilters;
Expand Down Expand Up @@ -40,6 +41,7 @@ pub fn init_docs(doc: &mut utoipa::openapi::OpenApi) {
ItemSortEnum,
WeaponData,
ArmorData,
ShieldData,
WeaponTypeEnum
))
)]
Expand Down
64 changes: 53 additions & 11 deletions src/services/shop_service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,26 +49,38 @@ pub async fn generate_random_shop_listing(
let shop_type = shop_data.shop_type.clone().unwrap_or_default();
let n_of_consumables: i64 = shop_data.consumable_dices.iter().map(|x| x.roll()).sum();
let n_of_equipables: i64 = shop_data.equipment_dices.iter().map(|x| x.roll()).sum();
let (n_of_equipment, n_of_armors, n_of_weapons) = match shop_type {
let (n_of_equipment, n_of_armors, n_of_weapons, n_of_shields) = match shop_type {
ShopTypeEnum::Blacksmith => {
// This will never panic if n_of_equipables >= 1 and dice sum should always be at least 1.
// if 1<=n<2 => n/2 = 0..n
// TLDR we know that it will never panic.
let n_of_forged_items = thread_rng().gen_range((n_of_equipables / 2)..=n_of_equipables);
let n_of_weapons = thread_rng().gen_range(0..=n_of_forged_items);
let n_of_armors = n_of_forged_items - n_of_weapons;
let forged_items_tuple = get_forged_items_tuple(n_of_forged_items);
(
n_of_equipables - n_of_forged_items,
n_of_weapons,
n_of_armors,
forged_items_tuple.0,
forged_items_tuple.1,
forged_items_tuple.2,
)
}
ShopTypeEnum::Alchemist => (n_of_equipables, 0, 0),
ShopTypeEnum::Alchemist => (n_of_equipables, 0, 0, 0),
ShopTypeEnum::General => {
let n_of_forged_items = thread_rng().gen_range(0..=(n_of_equipables / 2));
let n_of_weapons = thread_rng().gen_range(0..=n_of_forged_items);
let n_of_armors = n_of_forged_items - n_of_weapons;
// This can panic if n_of_equipables is <=1,
// n=1 => n/2 = 0, 0..0 panic!
// we manually set it as 1 in that case
let n_of_forged_items = thread_rng().gen_range(
0..=if n_of_equipables > 1 {
n_of_equipables / 2
} else {
1
},
);
let forged_items_tuple = get_forged_items_tuple(n_of_forged_items);
(
n_of_equipables - n_of_forged_items,
n_of_weapons,
n_of_armors,
forged_items_tuple.0,
forged_items_tuple.1,
forged_items_tuple.2,
)
}
};
Expand All @@ -84,6 +96,7 @@ pub async fn generate_random_shop_listing(
n_of_consumables,
n_of_weapons,
n_of_armors,
n_of_shields,
pathfinder_version,
},
)
Expand Down Expand Up @@ -111,6 +124,35 @@ pub async fn get_traits_list(app_state: &AppState) -> Vec<String> {
shop_proxy::get_all_possible_values_of_filter(app_state, ItemField::Traits).await
}

/// Gets the n of: weapons, armors, shields (in this order).
/// Changing order is considered a BREAKING CHANGE.
/// Calculating it randomly from the n of forged items.
fn get_forged_items_tuple(n_of_forged_items: i64) -> (i64, i64, i64) {
// This can panic if n=0.
// n<2 => 0..1, ok!
// n<1 => 0..0, panic!
// if that's the case we return 0 manually
let n_of_weapons = if n_of_forged_items > 0 {
thread_rng().gen_range(n_of_forged_items / 2..=n_of_forged_items)
} else {
0
};
let n_of_armors = n_of_forged_items - n_of_weapons;
// This can panic if we do not have enough armors (n<3).
// n<3 => 1..1, panic!
// n=3 => 1..2, ok!
// if that's the case we return 0 manually
// We take at least 1 shield if there are >3 armor
// (shield will never be > armor,
// with n>3 => (n/3)+1 is always < n
let n_of_shields = if n_of_armors >= 3 {
thread_rng().gen_range(1..(n_of_armors / 3) + 1)
} else {
0
};
(n_of_weapons, n_of_armors - n_of_shields, n_of_shields)
}

fn convert_result_to_shop_response(
field_filters: &ItemFieldFilters,
pagination: &ShopPaginatedRequest,
Expand Down

0 comments on commit 9406c09

Please sign in to comment.