Skip to content

Instantly share code, notes, and snippets.

@coderanger
Created January 9, 2022 03:20
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save coderanger/c69aaeaca5820af976e1278bfe50822c to your computer and use it in GitHub Desktop.
Save coderanger/c69aaeaca5820af976e1278bfe50822c to your computer and use it in GitHub Desktop.
FarmRPG crafting calculator
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