//! A recipe tracks the cost to produce an item.
use crate::{item::*, FcalcError};
use lazy_static::lazy_static;
use serde::Deserialize;
use std::{collections::HashMap, fmt, str::FromStr};
lazy_static! {
pub static ref RECIPES: Recipes = Recipes::new();
}
/// A formula for building a product
#[derive(Clone, Debug, Deserialize)]
pub struct RawRecipe {
inputs: Vec<String>,
output: String,
time: f64,
building_type: String,
}
/// A formula for building a product
#[derive(Clone, Debug, Deserialize)]
pub struct Recipe {
pub inputs: Vec<Item>,
pub output: Product,
time: f64,
building_type: BuildingType,
}
impl From<RawRecipe> for Recipe {
fn from(raw: RawRecipe) -> Self {
let inputs = raw
.inputs
.iter()
.map(|el| ITEMS.lookup(el).expect("Could not find input product"))
.collect();
let output = ITEMS
.lookup(&raw.output)
.expect("Could not find output product.")
.as_product()
.expect("Output product was a raw material");
Self {
inputs,
output,
time: raw.time,
building_type: BuildingType::from_str(&raw.building_type).unwrap(),
}
}
}
impl fmt::Display for Recipe {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"Inputs: {:?}, Time: {}, Building Type: {:?}",
self.inputs, self.time, self.building_type
)
}
}
/// Each recipe is made in a type of building
#[derive(Clone, Copy, Debug, Deserialize)]
pub enum BuildingType {
Assembler,
Furnace,
}
impl FromStr for BuildingType {
type Err = FcalcError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"Furnace" => Ok(BuildingType::Furnace),
"Assembler" => Ok(BuildingType::Assembler),
_ => Err(FcalcError::InvalidBuildingType),
}
}
}
/// Each building type has several tiers
#[derive(Debug, Deserialize)]
pub struct Building {
building_type: BuildingType,
name: String,
crafting_speed: f64,
}
/// Top-level to read recipes.toml
#[derive(Debug, Deserialize)]
pub struct RawRecipes {
buildings: Vec<Building>,
recipes: Vec<RawRecipe>,
}
impl RawRecipes {
fn new() -> Self {
Self::default()
}
}
impl Default for RawRecipes {
fn default() -> Self {
let input = include_str!("recipes.toml");
toml::from_str(input).expect("Could not read recipes input file.")
}
}
/// Recipe lookup
#[derive(Debug)]
pub struct Recipes {
recipes: HashMap<Product, Recipe>,
}
impl From<RawRecipes> for Recipes {
fn from(raw: RawRecipes) -> Self {
let mut recipes = HashMap::new();
for raw in &raw.recipes {
// TODO convert all Strings to Items in input and output
let recipe = Recipe::from(raw.clone());
recipes.insert(recipe.output.clone(), recipe);
}
Self { recipes }
}
}
impl Recipes {
fn new() -> Self {
let raw = RawRecipes::new();
Self::from(raw)
}
pub fn lookup(&self, product: &Product) -> Option<&Recipe> {
self.recipes.get(product)
}
}
#[cfg(test)]
mod test {
use super::*;
use pretty_assertions::assert_eq;
#[test]
fn it_loads_all_recipes() {
assert_eq!(RECIPES.recipes.len(), 1);
}
}