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 # Maximum number of each resource that user can input RESOURCE_CAP = 100000 # 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("Enter the number of snow golem heads you currently have:"), value = 10, min = 0, max = 1000), ui.input_numeric("golem_torsos", ui.HTML("Enter the number of snow golem torsos you currently have:"), value = 5, min = 0, max = 1000), ui.input_numeric("golem_limbs", ui.HTML("Enter the number of snow golem limbs you currently have:"), value = 30, min = 0, max = 1000), ui.input_numeric("ani_snow", ui.HTML("Enter the amount of animated snow you currently have:"), 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): @output @render.text 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() <= RESOURCE_CAP) else 'Invalid'}" @output @render.text 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() <= RESOURCE_CAP) else 'Invalid'}" @output @render.text 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() <= RESOURCE_CAP) else 'Invalid'}" @output @render.text 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() <= RESOURCE_CAP) else 'Invalid'}" # Perform the calculation for the maximum number of snow golems possible @reactive.Calc 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 @output @render.text def final_result(): return f"You can build a maximum of {calculate()[0]} golem(s). {calculate()[0] * '☃️'}" @output @render.text def golem_req(): return f"
  • 🙂 Head(s) needed: {calculate()[0]}
  • 👕 Torso(s) needed: {calculate()[0]}
  • 🦵 Limbs needed: {calculate()[0] * 4}
  • " @output @render.text def smashing_req(): return f"
  • 💥🙂 Smash head(s): {calculate()[1]}
  • 💥👕 Smash torso(s): {calculate()[2]}
  • 💥🦵 Smash limb(s): {calculate()[3]}
  • " @output @render.text def crafting_req(): return f"
  • 🛠️🙂 Craft head(s): {calculate()[4]}
  • 🛠️👕 Craft torso(s): {calculate()[5]}
  • 🛠️🦵 Craft limb(s): {calculate()[6]}
  • " @output @render.text def ani_snow_usage(): return f"
  • ➕❄️ Total Animated Snow available for use after smashing: {calculate()[7]}
  • ➖❄️ Animated Snow used to craft necessary golem parts: {calculate()[8]}
  • 🟰❄️ Remaining Animated Snow left over after smashing + crafting: {calculate()[9]}
  • " # Create app app = App(app_ui, server, debug = True)