Created
January 9, 2022 03:20
-
-
Save coderanger/c69aaeaca5820af976e1278bfe50822c to your computer and use it in GitHub Desktop.
FarmRPG crafting calculator
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import math | |
from typing import Optional | |
import attr | |
from frozendict import frozendict | |
@attr.s(auto_attribs=True, frozen=True) | |
class Item: | |
name: str | |
recipe: frozendict[str, int] = attr.ib(default=frozendict(), converter=frozendict) | |
sell_price: Optional[int] = None | |
buy_price: Optional[int] = None | |
craft_price: Optional[int] = None | |
passive_production: bool = False | |
class ItemDatabase: | |
items = [ | |
Item(name="Nails", buy_price=1), | |
Item(name="Iron", buy_price=50), | |
Item(name="Wood", passive_production=True), | |
Item(name="Board", recipe={"Wood": 5}, craft_price=1, passive_production=True), | |
Item(name="Stone", passive_production=True), | |
Item(name="Wood Plank", recipe={"Board": 4, "Nails": 4}, craft_price=2), | |
Item(name="Straw", passive_production=True), | |
Item( | |
name="Twine", | |
recipe={"Straw": 2, "Wood": 1}, | |
craft_price=10, | |
sell_price=150, | |
), | |
Item( | |
name="Sturdy Shield", | |
recipe={"Iron": 6, "Nails": 6, "Wood Plank": 1}, | |
craft_price=500, | |
sell_price=4000, | |
), | |
Item( | |
name="Iron Ring", | |
recipe={"Iron": 2, "Stone": 1}, | |
craft_price=10, | |
sell_price=110, | |
), | |
Item( | |
name="Fancy Pipe", | |
recipe={"Wood": 2, "Iron Ring": 3, "Iron": 1}, | |
craft_price=150, | |
sell_price=5000, | |
), | |
Item( | |
name="Unpolished Shimmer Stone", | |
sell_price=10, | |
craft_price=10, | |
passive_production=True, | |
), | |
Item( | |
name="Shimmer Stone", | |
sell_price=25, | |
craft_price=5, | |
recipe={"Unpolished Shimmer Stone": 2}, | |
), | |
Item( | |
name="Glass Orb", | |
sell_price=60, | |
craft_price=10, | |
recipe={"Shimmer Stone": 2, "Stone": 1}, | |
passive_production=True, | |
), | |
Item( | |
name="Glass Bottle", | |
sell_price=10, | |
craft_price=25, | |
recipe={"Glass Orb": 1, "Stone": 1}, | |
), | |
Item(name="Coal", sell_price=50, passive_production=True), | |
Item( | |
name="Lantern", | |
recipe={ | |
"Coal": 1, | |
"Glass Bottle": 1, | |
"Iron": 3, | |
"Iron Ring": 1, | |
"Twine": 2, | |
}, | |
craft_price=4000, | |
sell_price=40000, | |
), | |
Item( | |
name="Wagon Wheel", | |
sell_price=1750, | |
craft_price=250, | |
recipe={"Board": 12, "Nails": 14}, | |
), | |
Item( | |
name="Sturdy Sword", | |
sell_price=11000, | |
craft_price=1500, | |
recipe={"Iron": 2, "Leather": 1, "Mushroom Paste": 1, "Steel": 1}, | |
), | |
Item( | |
name="Mushroom Paste", sell_price=50, craft_price=2, recipe={"Mushroom": 3} | |
), | |
Item(name="Mushroom", sell_price=1, passive_production=True), | |
Item(name="Leather", sell_price=250, craft_price=25, recipe={"Hide": 2}), | |
Item(name="Hide", sell_price=150, passive_production=True), | |
Item( | |
name="Steel", | |
sell_price=800, | |
craft_price=250, | |
recipe={"Carbon Sphere": 1, "Glass Orb": 1, "Iron": 10}, | |
), | |
Item(name="Carbon Sphere", sell_price=500, passive_production=True), | |
Item(name="Sandstone", passive_production=True), | |
Item( | |
name="Sand", | |
sell_price=500, | |
craft_price=750, | |
recipe={"Leather": 1, "Sandstone": 5}, | |
), | |
Item( | |
name="Hourglass", | |
sell_price=25000, | |
craft_price=2000, | |
recipe={"Glass Bottle": 2, "Mushroom Paste": 2, "Sand": 1, "Wood": 6}, | |
), | |
Item(name="Cotton", sell_price=100000, recipe={"Cotton Seeds": 1}), | |
Item(name="Cotton Seeds", buy_price=84000), | |
Item( | |
name="Green Dye", | |
sell_price=35, | |
craft_price=2, | |
recipe={"Fern Leaf": 6, "Glass Bottle": 1}, | |
), | |
Item(name="Fern Leaf", sell_price=1, passive_production=True), | |
Item( | |
name="Green Cloak", | |
sell_price=125000, | |
craft_price=2500, | |
recipe={"Cotton": 1, "Green Dye": 10, "Leather": 5, "Twine": 10}, | |
), | |
Item( | |
name="Wooden Shield", | |
sell_price=500, | |
craft_price=75, | |
recipe={"Iron": 1, "Nails": 4, "Wood Plank": 1}, | |
), | |
Item( | |
name="Green Shield", | |
sell_price=5500, | |
craft_price=500, | |
recipe={"Green Dye": 1, "Iron": 3, "Wooden Shield": 1}, | |
), | |
Item(name="Salt Rock", sell_price=500, passive_production=True), | |
Item( | |
name="Salt", | |
sell_price=50000, | |
craft_price=10000, | |
recipe={"Hammer": 1, "Salt Rock": 50}, | |
), | |
Item( | |
name="Hammer", | |
sell_price=150, | |
craft_price=10, | |
recipe={"Board": 1, "Iron": 1, "Mushroom Paste": 1}, | |
), | |
Item( | |
name="Wooden Bow", | |
sell_price=2500, | |
craft_price=400, | |
recipe={"Fern Leaf": 1, "Iron": 2, "Twine": 2, "Wood": 4}, | |
), | |
Item( | |
name="Sturdy Bow", | |
sell_price=80000, | |
craft_price=25000, | |
recipe={"Mushroom Paste": 2, "Oak": 4, "Steel": 1, "Stone": 2, "Twine": 2} | |
), | |
Item(name="Oak", sell_price=100, passive_production=True), | |
Item( | |
name="Fancy Guitar", | |
sell_price=65000, | |
craft_price=8550, | |
recipe={"Iron": 4, "Mushroom Paste": 3, "Oak": 5, "Steel Wire": 6}, | |
), | |
Item( | |
name="Steel Wire", | |
sell_price=2000, | |
craft_price=100, | |
recipe={"Carbon Sphere": 1, "Iron": 10, "Stone": 1}, | |
), | |
] | |
items_by_name = {item.name: item for item in items} | |
def __getitem__(self, key: str) -> Item: | |
return self.items_by_name[key] | |
def expand( | |
self, | |
item: Item, | |
save_chance: float = 0, | |
craft_price_reduction: float = 0, | |
include_buyable: bool = False, | |
stop_at: set[Item] = set(), | |
) -> tuple[dict[Item, int], int]: | |
pool = [(item, 1)] | |
leaves: dict[Item, int] = {} | |
crating_cost = 0 | |
while pool: | |
cur_item, cur_count = pool.pop() | |
if cur_item.recipe and cur_item not in stop_at: | |
for name, count in cur_item.recipe.items(): | |
total_count = (cur_count * count) / (1 + save_chance) | |
pool.append((self[name], total_count)) | |
if cur_item.craft_price: | |
crating_cost += int( | |
math.ceil(cur_item.craft_price * (1 - craft_price_reduction)) | |
) | |
else: | |
count = leaves.get(cur_item, 0) | |
leaves[cur_item] = count + cur_count | |
if not include_buyable: | |
# Convert anything with a buy price to money. | |
for item, count in list(leaves.items()): | |
if item.buy_price: | |
crating_cost += item.buy_price * count | |
del leaves[item] | |
return leaves, crating_cost | |
def profit_per( | |
self, | |
target: Item, | |
per: Item, | |
mastered: set[Item] = set(), | |
grandmastered: set[Item] = set(), | |
**kwargs, | |
) -> Optional[float]: | |
if not target.sell_price: | |
return None | |
leaves, cost = self.expand(target, stop_at={per}, **kwargs) | |
if per not in leaves: | |
return None | |
price_multiplier = 1 | |
if target in grandmastered: | |
price_multiplier = 1.2 | |
elif target in mastered: | |
price_multiplier = 1.1 | |
profit = (target.sell_price * price_multiplier) - cost | |
return profit / leaves[per] | |
def profit_per_leaf(self, item: Item, **kwargs) -> dict[Item, Optional[float]]: | |
profits = { | |
leaf: self.profit_per(item, leaf, **kwargs) | |
for leaf in self.items | |
if leaf.passive_production | |
} | |
return {leaf: profit for leaf, profit in profits.items() if profit} | |
def all_profit_per_leaf(self, **kwargs) -> dict[Item, dict[Item, Optional[float]]]: | |
all_profits = { | |
item: self.profit_per_leaf(item, **kwargs) for item in self.items | |
} | |
return {item: profits for item, profits in all_profits.items() if profits} | |
db = ItemDatabase() | |
# profits = db.all_profit_per_leaf() | |
profits = db.all_profit_per_leaf( | |
save_chance=0.2, | |
craft_price_reduction=0.60, | |
mastered={ | |
db["Wood Plank"], | |
}, | |
grandmastered={ | |
db["Board"], | |
db["Iron Ring"], | |
db["Fancy Pipe"], | |
db["Sturdy Shield"], | |
db["Twine"], | |
}, | |
) | |
def print_forward(profits: dict[Item, dict[Item, Optional[float]]]): | |
for item, item_profits in sorted(profits.items(), key=lambda kv: kv[0].name): | |
print(f"{item.name}:") | |
for leaf, profit in sorted( | |
item_profits.items(), reverse=True, key=lambda kv: kv[1] | |
): | |
print(f"\t{int(math.floor(profit))}/{leaf.name}") | |
def print_reverse(profits: dict[Item, dict[Item, Optional[float]]]): | |
by_leaf: dict[Item, dict[Item, float]] = {} | |
for item, item_profits in profits.items(): | |
for leaf, profit in item_profits.items(): | |
by_leaf.setdefault(leaf, {})[item] = profit | |
print_forward(by_leaf) | |
print_forward(profits) | |
print("-------------") | |
print_reverse(profits) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment