Advent of Code F# – Day 22

You can become a serverless blackbelt. Enrol to my 4-week online workshop Production-Ready Serverless and gain hands-on experience building something from scratch using serverless technologies. At the end of the workshop, you should have a broader view of the challenges you will face as your serverless architecture matures and expands. You should also have a firm grasp on when serverless is a good fit for your system as well as common pitfalls you need to avoid. Sign up now and get 15% discount with the code yanprs15!

The source code for this post (both Part 1 and Part 2) is available here and you can click here to see my solutions for the other Advent of Code challenges.


Day 22‘s challenge essentially a more difficult version of Day 21, so we can roughly follow the same approach we took the last time round.

To get started, let’s define what the states for the player and the boss look like:


then we have the state of the entire game, comprising of the current states of the player and boss, as well as any effects from spells that are still active – we need to know the name of the spell, how to apply the effect, and how many turns it has left.

There are three spells that will leave lasting effects : shield, poison and rechargeThese effects can affect player state (shield and recharge) or boss state (poison), so I think the easiest way to represent an effect is as a function that takes in player and boss state and return their updated states.

Finally, we’re also defining a Result type to represent the two ways a game can end – player wins or boss wins.


But what about spells?

Well, spells can update player state (drain heals you, plus all spells costs mana), boss state (missile and drain damages the boss), as well as start effects (shield, poison and recharge) lasting several rounds. So, they can update all aspects of a game state, so basically a spell transforms a game state:


Next, in a nested module, let’s define our spells. But first, let’s add a couple of helper functions:


Magic Missile costs 53 mana. It instantly does 4 damage.


Drain costs 73 mana. It instantly does 2 damage and heals you for 2hit points.


Shield costs 113 mana. It starts an effect that lasts for 6 turns. While it is active, your armor is increased by 7.


Poison costs 173 mana. It starts an effect that lasts for 6 turns. At the start of each turn while it is active, it deals the boss 3 damage.


Recharge costs 229 mana. It starts an effect that lasts for 5 turns. At the start of each turn while it is active, it gives you 101 new mana.


and here are all the spells we can use during battle:


You start with 50 hit points and 500 mana points. The boss’s actual stats are in your puzzle input.

Based on the description of the challenge and my input file, this is how the player and boss state looks like at the start of the battle:


So far, we’ve added functions to damage the boss, let’s also add one to damage the player (taking into account the player’s armor value if there’s an active shield).

…if armor (from a spell, in this case) would reduce damage below 1, it becomes 1 instead – that is, the boss’ attacks always deal at least 1 damage.


Before each round, we also need to apply all the active effects. One tricky thing here is that because the shield effect is adding 7 to the player’s armor each time it’s applied, so we have to reset the player’s armor value first.

(aside : the shield effect should be setting the armor value to 7 rather than incrementing, which will remove the need to reset the armor value here. It happened here because I misread the description and thought the same spell can be cast twice and accumulate multiple shield effects…)


Regarding the above code, there’s an important paragraph to remember from the description of the challenge:

Effects apply at the start of both the player’s turns and the boss’ turns. Effects are created with a timer (the number of turns they last); at the start of each turn, after they apply any effect they have, their timer is decreased by one. If this decreases the timer to zero, the effect ends. You cannot cast a spell that would start an effect which is already active. However, effects can be started on the same turn they end.

Based on this paragraph, it means it’s ok for us to remove effects whose count becomes 0 after applying it (hence allowing the spell to be cast again), because effects can be started on the same turn they end.

Next, let’s also add a partial active pattern to check if it’s game over:


The simulation logic is also more complicated than Day 21, but it follows the high level structure in the snippet below

  • player and boss turns are represented by mutually recursive functions that tracks the current game state as well as the total mana used;
  • we’ll start the simulation with an initial game state using the provided player and boss state;
  • player attacks first


Now that you’ve seen the high-level structure of the runSim function, let’s drill down into what’s going on when it’s the player’s turn.

  • First, we apply any active effects, if an effect’s timer becomes 0 afterwards then it’s removed from the returned game state’s Effects list;
  • If either player or boss dies after that, then we can end the game straight away;
  • If the play can’t afford to cast any spells (the cheapest spell costs 53 mana) then he loses;
  • Otherwise, from all the spells we must make a choice between the spells that:
    • we can afford, and
    • it won’t start an effect that is still active
  • Using a brute force approach (no pruning), we will attempt each choice and collect the results
  • If the boss dies after our spell then we win! yay!
  • Otherwise, it’s now the boss’s turn..


When it’s the boss’s turn, again we have to first apply any active effects in the game. If either player or boss dies after that, then we can end the game straight away.

If not, we will hit the player, and if the player dies then the boss wins. Otherwise, we recurse back into the player’s turn.


Finally, to find the minimum amount of mana to still win the fight, we just run our brute force simulation, and find the smallest accumulated mana from the choices that lead to player winning.



Part 2

Just a tiny tweak required for Part 2 – to reduce the player’s HP by 1 at the start of a player round, as per the description.


Liked this article? Support me on Patreon and get direct help from me via a private Slack channel or 1-2-1 mentoring.
Subscribe to my newsletter

Hi, I’m Yan. I’m an AWS Serverless Hero and I help companies go faster for less by adopting serverless technologies successfully.

Are you struggling with serverless or need guidance on best practices? Do you want someone to review your architecture and help you avoid costly mistakes down the line? Whatever the case, I’m here to help.

Hire me.

Skill up your serverless game with this hands-on workshop.

My 4-week Production-Ready Serverless online workshop is back!

This course takes you through building a production-ready serverless web application from testing, deployment, security, all the way through to observability. The motivation for this course is to give you hands-on experience building something with serverless technologies while giving you a broader view of the challenges you will face as the architecture matures and expands.

We will start at the basics and give you a firm introduction to Lambda and all the relevant concepts and service features (including the latest announcements in 2020). And then gradually ramping up and cover a wide array of topics such as API security, testing strategies, CI/CD, secret management, and operational best practices for monitoring and troubleshooting.

If you enrol now you can also get 15% OFF with the promo code “yanprs15”.

Enrol now and SAVE 15%.

Check out my new podcast Real-World Serverless where I talk with engineers who are building amazing things with serverless technologies and discuss the real-world use cases and challenges they face. If you’re interested in what people are actually doing with serverless and what it’s really like to be working with serverless day-to-day, then this is the podcast for you.

Check out my new course, Learn you some Lambda best practice for great good! In this course, you will learn best practices for working with AWS Lambda in terms of performance, cost, security, scalability, resilience and observability. We will also cover latest features from re:Invent 2019 such as Provisioned Concurrency and Lambda Destinations. Enrol now and start learning!

Check out my video course, Complete Guide to AWS Step Functions. In this course, we’ll cover everything you need to know to use AWS Step Functions service effectively. There is something for everyone from beginners to more advanced users looking for design patterns and best practices. Enrol now and start learning!