Spaces:
Running
Running
import math | |
from shiny import App, reactive, render, ui | |
# Crafting requirements for golem parts | |
ANIMATED_SNOW_PER_HEAD_TORSO = 20 | |
ANIMATED_SNOW_PER_LIMB = 10 | |
# Animated snow given from smashing golem parts | |
ANIMATED_SNOW_SMASH_HEAD_TORSO = 10 | |
ANIMATED_SNOW_SMASH_PER_LIMB = 5 | |
# Define app UI | |
app_ui = ui.page_fluid( | |
ui.h1("☃️ Snow Golem Crafting Optimizer for MouseHunt (GWH 2023) ☃️"), | |
ui.layout_sidebar( | |
ui.sidebar( | |
ui.p("Adjust the user inputs to view the maximum number of snow golems you can make with your current resources."), | |
ui.input_numeric("golem_heads", ui.HTML("<b>Enter the number of snow golem heads you currently have:</b>"), value = 10, min = 0, max = 1000), | |
ui.input_numeric("golem_torsos", ui.HTML("<b>Enter the number of snow golem torsos you currently have:</b>"), value = 5, min = 0, max = 1000), | |
ui.input_numeric("golem_limbs", ui.HTML("<b>Enter the number of snow golem limbs you currently have:</b>"), value = 30, min = 0, max = 1000), | |
ui.input_numeric("ani_snow", ui.HTML("<b>Enter the amount of animated snow you currently have:</b>"), value = 15, min = 0, max = 1000), | |
width = 350 | |
), | |
ui.h3("User-selected inputs"), | |
ui.row( | |
ui.column(3, ui.output_text_verbatim("user_golem_heads")), | |
ui.column(3, ui.output_text_verbatim("user_golem_torsos")), | |
ui.column(3, ui.output_text_verbatim("user_golem_limbs")), | |
ui.column(3, ui.output_text_verbatim("user_ani_snow")) | |
), | |
ui.panel_conditional( | |
"!output.user_golem_heads.includes('Invalid') && !output.user_golem_torsos.includes('Invalid') && !output.user_golem_limbs.includes('Invalid') && !output.user_ani_snow.includes('Invalid') && input.golem_heads >= 0 && input.golem_torsos >= 0 && input.golem_limbs >= 0 && input.ani_snow >= 0 && input.golem_heads != null && input.golem_torsos != null && input.golem_limbs != null && input.ani_snow != null", | |
ui.h3("Maximum snow golems craftable with current resources"), | |
ui.output_text_verbatim("final_result"), | |
ui.hr(style = "border: none; border-top: 2px; margin-top: 10px; margin-bottom: 10px;"), | |
ui.h3("Detailed breakdown"), | |
ui.row( | |
ui.column(4, ui.h5("☃️ Golem Requirements"), ui.HTML(ui.output_ui("golem_req"))), | |
ui.column(4, ui.h5("💥 Smashing Requirements"), ui.HTML(ui.output_ui("smashing_req"))), | |
ui.column(4, ui.h5("🛠️ Crafting Requirements"), ui.HTML(ui.output_ui("crafting_req"))) | |
), | |
ui.hr(style = "border: none; border-top: 2px; margin-top: 10px; margin-bottom: 10px;"), | |
ui.h5("❄️ Animated Snow Usage"), ui.HTML(ui.output_ui("ani_snow_usage")) | |
) | |
), | |
) | |
# Define app server | |
def server(input, output, session): | |
def user_golem_heads(): | |
if input.golem_heads() is None: | |
return | |
return f"🙂 Head(s): {int(input.golem_heads()) if (input.golem_heads() >= 0 and input.golem_heads() <= 1000) else 'Invalid'}" | |
def user_golem_torsos(): | |
if input.golem_torsos() is None: | |
return | |
return f"👕 Torso(s): {int(input.golem_torsos()) if (input.golem_torsos() >= 0 and input.golem_torsos() <= 1000) else 'Invalid'}" | |
def user_golem_limbs(): | |
if input.golem_limbs() is None: | |
return | |
return f"🦵 Limb(s): {int(input.golem_limbs()) if (input.golem_limbs() >= 0 and input.golem_limbs() <= 1000) else 'Invalid'}" | |
def user_ani_snow(): | |
if input.ani_snow() is None: | |
return | |
return f"❄️ Anim. Snow: {int(input.ani_snow()) if (input.ani_snow() >= 0 and input.ani_snow() <= 1000) else 'Invalid'}" | |
# Perform the calculation for the maximum number of snow golems possible | |
def calculate(): | |
# Get user inputs | |
initial_animated_snow = input.ani_snow() | |
heads_in_inventory = input.golem_heads() | |
torsos_in_inventory = input.golem_torsos() | |
limbs_in_inventory = input.golem_limbs() | |
# Perform data validation | |
if any(elem is None or elem < 0 for elem in [initial_animated_snow, heads_in_inventory, torsos_in_inventory, limbs_in_inventory]): | |
return tuple([0] * 10) | |
# Change to suitable data type | |
initial_animated_snow = int(initial_animated_snow) | |
heads_in_inventory = int(heads_in_inventory) | |
torsos_in_inventory = int(torsos_in_inventory) | |
limbs_in_inventory = int(limbs_in_inventory) | |
# Amount of animated snow used per golem | |
animated_snow_per_golem = ANIMATED_SNOW_PER_HEAD_TORSO * 2 + ANIMATED_SNOW_PER_LIMB * 4 | |
# Calculate total animated snow equivalent units | |
animated_snow_eq_units = initial_animated_snow + (torsos_in_inventory + heads_in_inventory) * ANIMATED_SNOW_PER_HEAD_TORSO + limbs_in_inventory * ANIMATED_SNOW_PER_LIMB | |
# Theoretical maximum number of golems possible to be made based on the equivalent units of animated snow | |
res = math.floor(animated_snow_eq_units / animated_snow_per_golem) | |
# Scenario where golem parts need to be smashed to maximise number of snow golems built | |
if res < heads_in_inventory or res < torsos_in_inventory or res * 4 < limbs_in_inventory: | |
# Brute force method to determine how much of the excess part to smash | |
for i in range(1, max(heads_in_inventory + 1, torsos_in_inventory + 1, limbs_in_inventory // 4 + 1)): | |
# Start with smashing all but 1 of the largest excess piece then decrease from there | |
res = i | |
# Determine which golem parts that are to be smashed | |
smashed_heads = (heads_in_inventory - res) if res < heads_in_inventory else 0 | |
smashed_torsos = (torsos_in_inventory - res) if res < torsos_in_inventory else 0 | |
smashed_limbs = (limbs_in_inventory - res * 4) if res * 4 < limbs_in_inventory else 0 | |
# Get value of animated snow from smashing golem parts | |
animated_snow_from_smashing = (smashed_heads + smashed_torsos) * ANIMATED_SNOW_SMASH_HEAD_TORSO + smashed_limbs * ANIMATED_SNOW_SMASH_PER_LIMB | |
# Get the revised amount of animated snow that need to be used to craft the snow golems | |
crafted_heads = (res - heads_in_inventory) if heads_in_inventory < res else 0 | |
crafted_torsos = (res - torsos_in_inventory) if torsos_in_inventory < res else 0 | |
crafted_limbs = (res * 4 - limbs_in_inventory) if limbs_in_inventory < res * 4 else 0 | |
animated_snow_for_crafting = initial_animated_snow + animated_snow_from_smashing | |
# Check if available animated snow is insufficient to craft necessary snow golem parts, then go back 1 iteration (That will be the maximum of snow golems that are buildable with current resource constraints) | |
if animated_snow_for_crafting < (crafted_heads + crafted_torsos) * ANIMATED_SNOW_PER_HEAD_TORSO + crafted_limbs * ANIMATED_SNOW_PER_LIMB: | |
# Go to previous iteration | |
res -= 1 | |
# Determine which golem parts that are needed to be smashed | |
smashed_heads = (heads_in_inventory - res) if res < heads_in_inventory else 0 | |
smashed_torsos = (torsos_in_inventory - res) if res < torsos_in_inventory else 0 | |
smashed_limbs = (limbs_in_inventory - res * 4) if res * 4 < limbs_in_inventory else 0 | |
# Get value of animated snow from smashing golem parts | |
animated_snow_from_smashing = (smashed_heads + smashed_torsos) * ANIMATED_SNOW_SMASH_HEAD_TORSO + smashed_limbs * ANIMATED_SNOW_SMASH_PER_LIMB | |
# Get the revised amount of animated snow that need to be used to craft the snow golems | |
crafted_heads = (res - heads_in_inventory) if heads_in_inventory < res else 0 | |
crafted_torsos = (res - torsos_in_inventory) if torsos_in_inventory < res else 0 | |
crafted_limbs = (res * 4 - limbs_in_inventory) if limbs_in_inventory < res * 4 else 0 | |
animated_snow_for_crafting = initial_animated_snow + animated_snow_from_smashing | |
break | |
else: # If no golem parts need to be smashed | |
smashed_heads, smashed_torsos, smashed_limbs = 0, 0, 0 | |
animated_snow_for_crafting = initial_animated_snow | |
animated_snow_from_smashing = 0 | |
crafted_heads = max(res - heads_in_inventory, 0) | |
crafted_torsos = max(res - torsos_in_inventory, 0) | |
crafted_limbs = max(res * 4 - limbs_in_inventory, 0) | |
# Calculate total amount of animated snow used | |
used_animated_snow = crafted_heads * ANIMATED_SNOW_PER_HEAD_TORSO + crafted_torsos * ANIMATED_SNOW_PER_HEAD_TORSO + crafted_limbs * ANIMATED_SNOW_PER_LIMB | |
leftover_animated_snow = initial_animated_snow + animated_snow_from_smashing - used_animated_snow | |
return res, smashed_heads, smashed_torsos, smashed_limbs, crafted_heads, crafted_torsos, crafted_limbs, animated_snow_for_crafting, used_animated_snow, leftover_animated_snow | |
def final_result(): | |
return f"You can build a maximum of {calculate()[0]} golem(s). {calculate()[0] * '☃️'}" | |
def golem_req(): | |
return f"<li>🙂 Head(s) needed: {calculate()[0]}</li><li>👕 Torso(s) needed: {calculate()[0]}</li><li>🦵 Limbs needed: {calculate()[0] * 4}</li>" | |
def smashing_req(): | |
return f"<li>💥🙂 Smash head(s): {calculate()[1]}</li><li>💥👕 Smash torso(s): {calculate()[2]}</li><li>💥🦵 Smash limb(s): {calculate()[3]}</li>" | |
def crafting_req(): | |
return f"<li>🛠️🙂 Craft head(s): {calculate()[4]}</li><li>🛠️👕 Craft torso(s): {calculate()[5]}</li><li>🛠️🦵 Craft limb(s): {calculate()[6]}</li>" | |
def ani_snow_usage(): | |
return f"<li>➕❄️ Total Animated Snow available for use after smashing: {calculate()[7]}</li><li>➖❄️ Animated Snow used to craft necessary golem parts: {calculate()[8]}</li><li>🟰❄️ Remaining Animated Snow left over after smashing + crafting: {calculate()[9]}</li>" | |
# Create app | |
app = App(app_ui, server, debug = True) | |