Skip to content

Commit

Permalink
feat:
Browse files Browse the repository at this point in the history
* introduce random shop
* split raw query in sub methods
* introduce dice structure
  • Loading branch information
RakuJa committed May 26, 2024
1 parent debb3b2 commit ccf6e56
Show file tree
Hide file tree
Showing 12 changed files with 285 additions and 28 deletions.
65 changes: 58 additions & 7 deletions src/db/data_providers/raw_query_builder.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,38 @@
use crate::models::creature::creature_filter_enum::CreatureFilter;
use crate::models::item::item_metadata::type_enum::ItemTypeEnum;
use crate::models::shop_structs::ShopFilterQuery;
use log::debug;
use std::collections::{HashMap, HashSet};

const ACCURACY_THRESHOLD: i64 = 50;

pub fn prepare_filtered_get_items(shop_filter_query: &ShopFilterQuery) -> String {
let n_of_equipment = shop_filter_query.n_of_equipment;
let n_of_consumables = shop_filter_query.n_of_consumables;
let supported_pf_versions =
HashSet::from_iter(shop_filter_query.pathfinder_version.to_db_value());
let min_level = shop_filter_query.min_level as i64;
let max_level = shop_filter_query.max_level as i64;
let equipment_query = prepare_item_subquery(
&ItemTypeEnum::Equipment,
n_of_equipment,
min_level,
max_level,
&supported_pf_versions,
);
let consumable_query = prepare_item_subquery(
&ItemTypeEnum::Consumable,
n_of_consumables,
min_level,
max_level,
&supported_pf_versions,
);
let query = format!(
"SELECT * FROM ITEM_TABLE WHERE id IN ( {equipment_query} ) OR id IN ({consumable_query} )"
);
debug!("{}", query);
query
}
pub fn prepare_filtered_get_creatures_core(
key_value_filters: &HashMap<CreatureFilter, HashSet<String>>,
) -> String {
Expand Down Expand Up @@ -40,7 +70,7 @@ pub fn prepare_filtered_get_creatures_core(
simple_core_query.push_str(" AND ")
}
simple_core_query
.push_str(prepare_bounded_check(value, ACCURACY_THRESHOLD, 100).as_str())
.push_str(prepare_bounded_or_check(value, ACCURACY_THRESHOLD, 100).as_str())
}
_ => (),
}
Expand All @@ -57,9 +87,9 @@ pub fn prepare_filtered_get_creatures_core(
query
}

/// Prepares a 'bounded AND statement' aka checks if all the columns are in the bound given
/// (brute_percentage >= 0 AND brute_percentage <= 0) AND (sniper_percentage >= 0 ...) ...
fn prepare_bounded_check(
/// Prepares a 'bounded OR statement' aka checks if all the columns are in the bound given
/// (brute_percentage >= 0 AND brute_percentage <= 0) OR (sniper_percentage >= 0 ...) ...
fn prepare_bounded_or_check(
column_names: &HashSet<String>,
lower_bound: i64,
upper_bound: i64,
Expand All @@ -69,13 +99,17 @@ fn prepare_bounded_check(
if !bounded_query.is_empty() {
bounded_query.push_str(" OR ");
}
bounded_query.push_str(
format!("({column} >= {lower_bound} AND {column} <= {upper_bound})").as_str(),
);
bounded_query
.push_str(prepare_bounded_check(column.as_str(), lower_bound, upper_bound).as_str());
}
bounded_query
}

/// Prepares a 'bounded statement' aka (x>=lb AND x<=ub)
fn prepare_bounded_check(column: &str, lower_bound: i64, upper_bound: i64) -> String {
format!("({column} >= {lower_bound} AND {column} <= {upper_bound})")
}

/// Prepares a query that gets all the ids linked with a given list of traits, example
/// SELECT tcat.creature_id
/// FROM TRAIT_CREATURE_ASSOCIATION_TABLE tcat
Expand Down Expand Up @@ -139,3 +173,20 @@ fn prepare_in_statement_for_generic_type(
}
result_string
}
fn prepare_item_subquery(
item_type: &ItemTypeEnum,
n_of_item: i64,
min_level: i64,
max_level: i64,
supported_pf_version: &HashSet<String>,
) -> String {
let item_type_query = prepare_get_id_matching_item_type_query(item_type);
let initial_statement = "SELECT id FROM ITEM_TABLE";
let filter_by_version = prepare_in_statement_for_generic_type("remaster", supported_pf_version);
let filter_by_level = prepare_bounded_check(&String::from("level"), min_level, max_level);
format!("{initial_statement} WHERE {filter_by_level} AND {filter_by_version} AND id IN ( {item_type_query} ) ORDER BY RANDOM() LIMIT {n_of_item}")
}

fn prepare_get_id_matching_item_type_query(item_type: &ItemTypeEnum) -> String {
format!("SELECT id FROM ITEM_TABLE WHERE UPPER(item_type) = UPPER('{item_type}')")
}
45 changes: 44 additions & 1 deletion src/db/data_providers/shop_fetcher.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
use crate::db::data_providers::generic_fetcher::MyString;
use crate::db::data_providers::raw_query_builder::prepare_filtered_get_items;
use crate::models::db::raw_trait::RawTrait;
use crate::models::item::item_metadata::type_enum::ItemTypeEnum;
use crate::models::item::item_struct::Item;
use crate::models::routers_validator_structs::PaginatedRequest;
use crate::models::shop_structs::ShopFilterQuery;
use anyhow::Result;
use sqlx::{Pool, Sqlite};
use log::debug;
use rand::Rng;
use sqlx::{query_as, Pool, Sqlite};

pub async fn fetch_item_by_id(conn: &Pool<Sqlite>, item_id: i64) -> Result<Item> {
let mut item: Item =
Expand Down Expand Up @@ -64,3 +69,41 @@ async fn update_items_with_traits(conn: &Pool<Sqlite>, mut items: Vec<Item>) ->
}
items
}

pub async fn fetch_items_with_filters(
conn: &Pool<Sqlite>,
filters: &ShopFilterQuery,
) -> Result<Vec<Item>> {
let result: Vec<Item> = query_as(prepare_filtered_get_items(filters).as_str())
.fetch_all(conn)
.await?;
let equipment: Vec<Item> = result
.iter()
.filter(|x| x.item_type == ItemTypeEnum::Equipment)
.cloned()
.collect();
let consumables: Vec<Item> = result
.iter()
.filter(|x| x.item_type == ItemTypeEnum::Consumable)
.cloned()
.collect();

if result.len() as i64 >= filters.n_of_consumables + filters.n_of_equipment {
debug!("Result vector is the correct size, no more operations needed");
return Ok(result);
}
debug!("Result vector is not the correct size, duplicating random elements..");
// We clone, otherwise we increment the probability of the same item being copied n times
let mut item_vec = result.clone();
for _ in 0..(equipment.len() as i64 - filters.n_of_equipment) {
if let Some(x) = equipment.get(rand::thread_rng().gen_range(0..equipment.len())) {
item_vec.push(x.clone());
}
}
for _ in 0..(consumables.len() as i64 - filters.n_of_consumables) {
if let Some(x) = consumables.get(rand::thread_rng().gen_range(0..consumables.len())) {
item_vec.push(x.clone());
}
}
Ok(result)
}
8 changes: 8 additions & 0 deletions src/db/shop_proxy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use crate::models::creature::creature_metadata::type_enum::CreatureTypeEnum;
use crate::models::item::item_fields_enum::{FieldsUniqueValuesStruct, ItemField};
use crate::models::item::item_struct::Item;
use crate::models::routers_validator_structs::{ItemFieldFilters, PaginatedRequest};
use crate::models::shop_structs::ShopFilterQuery;
use crate::AppState;
use anyhow::Result;
use cached::proc_macro::once;
Expand All @@ -14,6 +15,13 @@ pub async fn get_item_by_id(app_state: &AppState, id: i64) -> Option<Item> {
.ok()
}

pub async fn get_filtered_items(
app_state: &AppState,
filters: &ShopFilterQuery,
) -> Result<Vec<Item>> {
shop_fetcher::fetch_items_with_filters(&app_state.conn, filters).await
}

pub async fn get_paginated_items(
app_state: &AppState,
filters: &ItemFieldFilters,
Expand Down
2 changes: 2 additions & 0 deletions src/models/encounter_structs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,9 @@ pub struct RandomEncounterData {
pub creature_types: Option<Vec<CreatureTypeEnum>>,
pub creature_roles: Option<Vec<CreatureRoleEnum>>,
pub challenge: Option<EncounterChallengeEnum>,
#[validate(range(min = 1, max = 30))]
pub min_creatures: Option<u8>,
#[validate(range(min = 1, max = 30))]
pub max_creatures: Option<u8>,
#[validate(length(min = 1))]
pub party_levels: Vec<i64>,
Expand Down
18 changes: 16 additions & 2 deletions src/models/item/item_metadata/type_enum.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
use serde::{Deserialize, Serialize};
use sqlx::Type;
use std::fmt::{Display, Formatter};
use std::str::FromStr;
use strum::{Display, EnumIter};
use strum::EnumIter;
use utoipa::ToSchema;

#[derive(
Serialize, Deserialize, ToSchema, Display, Eq, Hash, PartialEq, Ord, PartialOrd, Type, EnumIter,
Serialize, Deserialize, ToSchema, Eq, Hash, PartialEq, Ord, PartialOrd, Type, EnumIter,
)]
pub enum ItemTypeEnum {
#[serde(alias = "consumable", alias = "CONSUMABLE")]
Expand Down Expand Up @@ -33,3 +34,16 @@ impl FromStr for ItemTypeEnum {
}
}
}

impl Display for ItemTypeEnum {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
ItemTypeEnum::Consumable => {
write!(f, "consumable")
}
ItemTypeEnum::Equipment => {
write!(f, "equipment")
}
}
}
}
2 changes: 1 addition & 1 deletion src/models/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@ pub mod pf_version_enum;
pub mod response_data;
pub mod routers_validator_structs;
pub mod scales_struct;
mod shop_structs;
pub mod shop_structs;
14 changes: 14 additions & 0 deletions src/models/pf_version_enum.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,17 @@ pub enum PathfinderVersionEnum {
#[default]
Any,
}

impl PathfinderVersionEnum {
pub fn to_db_value(&self) -> Vec<String> {
match self {
// The db column is a boolean called "remaster" so we translate the enum to
// FALSE if legacy, TRUE if remaster and TRUE, FALSE if both
PathfinderVersionEnum::Legacy => vec![String::from("FALSE")],
PathfinderVersionEnum::Remaster => vec![String::from("TRUE")],
PathfinderVersionEnum::Any => {
vec![String::from("TRUE"), String::from("FALSE")]
}
}
}
}
21 changes: 20 additions & 1 deletion src/models/routers_validator_structs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ use crate::models::creature::creature_metadata::size_enum::SizeEnum;
use crate::models::creature::creature_metadata::type_enum::CreatureTypeEnum;
use crate::models::item::item_metadata::type_enum::ItemTypeEnum;
use crate::models::pf_version_enum::PathfinderVersionEnum;
use rand::Rng;
use serde::{Deserialize, Serialize};
use utoipa::IntoParams;
use utoipa::{IntoParams, ToSchema};
use validator::Validate;

#[derive(Serialize, Deserialize, IntoParams, Validate)]
Expand Down Expand Up @@ -86,3 +87,21 @@ impl Default for PaginatedRequest {
}
}
}

#[derive(Serialize, Deserialize, ToSchema, Validate, Eq, PartialEq, Hash, Clone)]
pub struct Dice {
#[validate(range(min = 1, max = 255))]
pub n_of_dices: u8,
#[validate(range(min = 2, max = 255))]
pub dice_size: u8,
}

impl Dice {
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
}
roll_result
}
}
37 changes: 35 additions & 2 deletions src/models/shop_structs.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,39 @@
use crate::models::pf_version_enum::PathfinderVersionEnum;
use crate::models::routers_validator_structs::Dice;
use serde::{Deserialize, Serialize};
use strum::EnumIter;
use utoipa::ToSchema;
use validator::Validate;

#[derive(Serialize, Deserialize, ToSchema, Validate)]
pub struct RandomShopData {}
#[derive(
Serialize, Deserialize, ToSchema, Default, EnumIter, Eq, PartialEq, Hash, Ord, PartialOrd, Clone,
)]
pub enum ShopTypeEnum {
Blacksmith,
Alchemist,
#[default]
General,
}

#[derive(Serialize, Deserialize, ToSchema, Validate, Clone)]
pub struct RandomShopData {
#[validate(range(max = 30))]
pub min_level: Option<u8>,
#[validate(range(max = 30))]
pub max_level: Option<u8>,
#[validate(length(min = 1))]
pub equipment_dices: Vec<Dice>,
#[validate(length(min = 1))]
pub consumable_dices: Vec<Dice>,
pub shop_type: Option<ShopTypeEnum>,
pub pathfinder_version: Option<PathfinderVersionEnum>,
}

pub struct ShopFilterQuery {
//pub shop_type: ShopTypeEnum,
pub min_level: u8,
pub max_level: u8,
pub n_of_equipment: i64,
pub n_of_consumables: i64,
pub pathfinder_version: PathfinderVersionEnum,
}
Loading

0 comments on commit ccf6e56

Please sign in to comment.