In case you missed the announcement: The Alteryx One Fall Release is here! Learn more about the new features and capabilities here
Start Free Trial

Community News

Community news, customer stories, and more!
Hub119
12 - Quasar
12 - Quasar

 

Advent of Code is a month-long, problem-solving, programming adventure that many of us Alteryx enthusiasts jump into each December to tackle with BaseA (AOC 2025).  Along those journeys, there are many whacky challenges to solve, and if you’ve been following this series, you already know that Games happen to be one of the repeated themes.  We have already covered Card & Dice Games as well as Arcade Games. Now, it’s time to change gears once again and check out some cases where Advent of Code brings users into the realm of Role-Playing Games (RPGs).  Buckle up readers, we have quite an adventure ahead of us!

 

AoC RPGs

 

Going as far back as the very first year of AoC in 2015, in back-to-back days you are challenged with creating RPG combat simulators.  For the first challenge you are tasked with battling a boss while optimizing equipment choices that boost offensive and defensive stats.

 

Hub119_0-1764010565207.png

 

 

 

Your goal is to find both the least amount of gold you can spend to buy the necessary equipment to win your battle and also the most you can spend without being victorious.  To do so, you are instructed to build out a combat simulation that uses the purchased/equipped items (and relative stat boosts) and has the player character and boss trade blows until one or the other is victorious (aka the opponent is reduced to 0 health points).  In reality, with Alteryx we can come to a much simpler solution by using a series of data appends to assemble all combinations of weapons, armor and rings.  In doing so, we can calculate total equipment cost and the overall impact on character stats.  This in turn allows us to calculate how much damage both the player and the boss would receive on a given turn, which then allows us to calculate via a simple formula how many turns each can survive until death.  A simple comparison of turn count then tells us if the selected equipment purchase would result in eventual victory or defeat without ever needing to actually iterate through turns of combat!

 

Hub119_1-1764010598946.png

 

 

If you have been following along with this blog series, and in case you can’t tell from the above DAY 21 workflow, it should be noted that the first year of Advent of Code was WAY easier!  I mean, this looks a lot more like something we would all build for a Weekly Challenge solution than what AYXxAoC faithful have come to expect at the tail end of each year’s set of problems.

On the following day things get a bit more interesting as you trade in your sword for a wizard staff.  Now, instead of merely trading blows with affixed stat levels, you have to account for an array of spells at your disposal (Magic Missile, Drain, Shield, Poison and Recharge – all with unique mana costs and effects).

 

ExpelliarmusGIF.gif

Here you are tasked with finding the optimal sequence of spells to cast (and minimum mana/magic point usage) to survive and win the fight both on “normal” and “hard” (the player’s life is constantly drained) difficulties.  These two “game modes” form Parts 1 and 2 of the problem.  For this round, it was time to actually simulate some combat, and to do so, I broke out some iterative macros:

 

Hub119_3-1764010598958.png

 

 

What is going on inside those magical wizard combat macros you might ask?  Well, for starters, other than a couple slight changes to account for constant player HP reduction (aka [Player HP]-1 to start every turn) they are identical.  And secondly, it’s actually much  less magic than you might think.  The real key to this challenge was the Formula tool highlighted just before these macros that allows us to establish all the variables we need to track, including statuses and the turn counts that they have been active.  Within the macro itself, all we then really need to do for each iteration is apply a set of formulas given current tracked states (such as an active poison spell) to adjust player and boss status and health, append a new record for each potential spell cast each turn, and filter out records that would lead to end states (player or boss deaths or running out of available mana) or unavailable spell casting (like a poison spell if one is already active).

 

 

Hub119_4-1764010598963.png

 

 

The other key is to find a way to limit your iterative record count – as is the case with most AoC problems.  In this situation, we track the lowest mana usage of any and all victory states once achieved.  If a new “win” later occurs that spends less mana points throughout the course of battle you replace the saved “best” win score for comparison, while removing all in progress games that have already spent more mana than what was expended in your best achieved victory.  With that, in barely any time at all we can run thousands of battle simulations in mere seconds and arrive at our desired results.

 

---

 

All right, now that we seem to have a handle on basic RPG combat, why don’t we delve into some movement mechanics and factor in some additional combatants while we’re at it?  To do so, we will travel back to one of my all-time favorite Advent of Code problems, 2018’s Day 15, to partake in an epic battle to the death between elves and goblins… over some hot chocolate.

 

Hub119_5-1764010598974.png

 

 

In this particular scenario, a team of elves happen to be in a cave, trying to enjoy the aforementioned hot chocolate you helped them perfect the day prior, when they come under attack by an opposing faction of goblins.  Everyone’s initial position within the cave just so happens to be the input you start with, which will look something like this:

 

Hub119_6-1764010598995.png

 

In my case at least, our elf friends were outnumbered 2 to 1… not very good odds when both elves and goblins each start with 200 hit points (HP) and an attack power of 3 (deal 3 damage per hit).  Eventually (Part 2 of the problem) we will do some slight tampering to the space-time continuum to aid our elvish friends in battle (did I mention the AoC 2018 storyline involves time travel?), but first let’s establish how this battle would play out to begin with by understanding how movement and combat work.  Afterall, we still need some semblance of order… this is supposed to be a programming problem after all.  As stated in the problem text: “Combat proceeds in rounds; in each round, each unit that is still alive takes a turn, resolving all of its actions before the next unit's turn begins. On each unit's turn, it tries to move into range of an enemy (if it isn't already) and then attack (if it is in range).”  “If no targets remain, combat ends.”  “All units are very disciplined and always follow very strict combat rules. Units never move or attack diagonally, as doing so would be dishonorable.”  See?  Like I said, luckily, we have some order and discipline to follow here.

 

To begin with, within each round of battle, each unit on the map will take their turns in “reading order” based upon the “reading order” of where they currently reside on the map:

 

Hub119_7-1764010598997.png

 

 

By extracting the x and y coordinates of each unit’s position, we can very quickly determine the order in which turns will proceed on a given round.  In fact, let’s start this problem the same way I start any map/maze movement problem in AoC: use a Record ID tool to label input records as Y values, tokenize the data splitting to rows with the RegEx tool (using the regular expression “.”), and then using a second Record ID tool, grouping on the Y value to add the x-coordinate of each datapoint:

 

Hub119_8-1764010599000.png

 

 

It should be noted that as of the 2024.2 release of Alteryx Designer, this group by functionality is embedded within the Record ID tool.  If you are using previous versions of Designer still, you can use a multi-row formula or tile tool to produce similar results.  Anyhow, now that we have established x and y coordinates to all components on the map, filtering to the records of our elves (E’s) and goblins (G’s) and sorting on Y and X values allows us to quickly establish the order in which units will take their turns.

 

Now that we know how to determine turn order, what does each unit do on a given turn?  Well, if an enemy combatant is within striking distance (checking spaces up, down, left, and right of the unit’s position… more on this later) then we attack.  If not, we need to establish where to move next in order to approach and get in range to attack an enemy unit.  First, we have to look at all available targets, determine which adjacent attack locations are reachable, and then choose which location to start moving towards based upon both proximity and (again) reading order (example below used to determine movement choice of the single elf “E” unit displayed):

 

 

Hub119_9-1764010599001.png

 

 

If that wasn’t enough, once a target endpoint is selected on a given turn, that turn is then spent taking a single step in the direction of the chosen destination.  As there may be multiple ways to reach said destination, we also need to evaluate which first step is the one that needs to be taken:

 

 

Hub119_10-1764010599009.png

 

 

While simple enough to follow in theory at least, this is where things begin to become way more complex in nature when it comes to sorting out how the heck to program this behavior (even with drag and drop tools!).  In order to evaluate which target to move towards and which step to take, we have to actually find every path to every available target!  …and somehow do this automatically?  Lucky for me, this was not my first or only foray into AoC map/maze movement.  In fact, I could write an entire blog (probably even longer than this one) on all the different types of maze solves that one will encounter across the many years of Advent of Code: “normal” mazes (similar to what we need to do here), locked door key mazes, sequential checkpoint mazes, a case where you can phase through walls to “cheat” the maze, a donut mazes, a 3D cube maze, etc.  In nearly all of them, however, some version of a Breadth-First Search (BFS) approach tends to work best.

 

ExcuseMeReallyGIF.gif

 

As Google AI will tell you: “Breadth-First Search (BFS) is a graph traversal algorithm that explores nodes level by level, visiting all neighbors at the current depth before moving to the next level. It is often used to find the shortest path in an unweighted graph because it guarantees finding the shortest path to a target node.”  In other words, beginning with a starting location (x/y coordinate pair), we need to track both where we have been (so we don’t circle back to a previous spot) and where we can move next from a given location.  First, where we have been.  One way to do this is to create a string field that tracks the path (i.e the coordinates) we have already traversed.  For example, if a unit started at position x=5, y=10 then we write something like “(5,10)” into our path field initially.  If our next turn takes us right one space to x=6, y=10 we update the path field to “(5,10)(6,10)” which allows us to both easily parse later based on the construction of each coordinate set and search (using a Contains function) to determine whether our path already contains a location we are attempting to move into.  Personally, I like to use this naming convention as the parentheses are used as part of my contains search to ensure that I am only looking for the full location coordinate set and not a partial match (we don’t want to mistake looking for “1,5” with “11,52”).  Okay, so what does this all look like in execution?

 

 

Hub119_12-1764010599055.png

 

 

For starters, it should be noted that this is all packaged within an iterative macro, as we will need to take multiple steps to reach our destinations, and with each iteration we can take a single step.  To do so, we can begin with a Generate Rows tool, essentially setting up our options to move in one of four directions (up, down, left, right).  The following Formula tool then translates our 4 options into the new X and Y coordinates we are considering moving to.  The Filter tool uses that aforementioned Contains (or rather does not contain: “!Contains”) function to get rid of any generated records that would return us to a location we have already visited.  The Join tool then matches to available spaces (x/y coordinate pairs) as we can’t move into spaces that are already occupied, whether they be walls or other units.  Finally, another Formula tool updates our Path field to track our new movement.  What isn’t displayed here, is a Select tool removing our old “CurrentX” and “CurrentY” fields and replacing them with (and renaming) the “NewX” and “NewY” fields to then use for subsequent iterations.  There are a number of other tools involved (even just in this movement macro), but hopefully that gives you a general idea of how to approach these maze/map movement problems.  The idea is to continue building paths (while pruning any that run into a dead end and/or circle back on themselves) until you reach a desired end goal location.  Because this algorithm will build out one step at a time (in every possible direction) with each iteration, as soon as your target is reached you know that is the shortest path to get there.  In this particular case, because on each turn, there are so many available target locations to ultimately try and move towards, I decided to implement a bidirectional search as well.  Without getting into too many details, essentially what that means is I am building paths from the start node and the end node(s) simultaneously and seeing if they meet in the middle.  This will cut your search iterations in half (per start/goal location pair) but is only advised if you have the memory to spare as it can become much more computationally expensive as you can quickly explode your iterative record counts.

 

Hub119_13-1764010599062.png

 

All right, back to our Elves and Goblins doing battle in the cave.  Now that we have a way to assess all available paths to all available targets, thus determining both the closest proximity destinations (shortest path), chosen destination (reading order of equidistant destinations), and next step on the path (reading order of equidistant first step options on available shortest paths to chosen destination), we can get ONE of the units on the map to take a SINGLE turn and move a SINGLE step.  But wait, before we expand this out to ALL unit turns in EVERY ROUND of battle, we are forgetting something… oh right, the actual COMBAT!  After a unit moves in range of an opposing unit (remember, if already in range, it doesn’t move to begin with) it attacks.  Similar to movement, combat target selection also follows a set of rules.  If more than one enemy combatant is within range, the one with the least remaining HP is targeted.  If more than one is in range AND they both have the same HP, we again do a tiebreaker based upon map location reading order.  With the target selected, the unit strikes and its attack power is subtracted from the target’s health pool.  If this results in a target’s death (HP being reduced to 0) that unit is removed from the map, no longer takes any turns (it is dead 💀), and the space it was occupying becomes open.

 

Okay, so how do we bring this all together?  In our main workflow we start by converting our input map into a more usable data stream (x/y coordinates) to establish our overall map as well as the initial state of all units (location, health points, attack power, and number of rounds completed – used in calculating our final answer):

 

Hub119_14-1764010599065.png

 

 

With our initial data prep out of the way, we feed into an iterative macro that tracks what occurs in each round of battle (within red box above):

 

 

Hub119_15-1764010599068.png

 

 

At the start of each round, we use each unit’s position to determine turn order, and then feed our data into another iterative macro (again, red box above) that controls what occurs within each unit’s turn:

 

 

Hub119_16-1764010599072.png

 

 

Here, as you can likely surmise from the eye chart above, a number of different events can play out that we must account for.  First (within the container to the top left), we check if an enemy unit is already within striking range, and if so, the unit does not move, selects the appropriate target (based on selection criteria discussed previously) and strikes.  Next, we check if there are open spaces available around any enemy target units.  If there are none, the unit stays in place and ends its turn.  Otherwise, we then determine the available paths to all enemy units with open spaces available around them (more on this in a sec).  If no paths are available to reach them, the unit does not move and its turn ends.  If there is at least one available path, using the selection criteria detailed above, the unit selects the destination location and moves one space towards it.  If now within striking range the unit also attacks and if not, its turn merely ends.  Okay, back to determining the available paths… for that we need to go yet another level deeper into a 3rd nested iterative macro (again, see red box in above image), to finally perform that BFS/Bidirectional Search I mentioned previously:

 

Hub119_17-1764010599077.png

 

Stitching all of this together, we can finally let our battle play out to determine a victor.  Adding some map renders to our workflows (not pictured above) we can see how this plays out:

 

Animated Battle.gif

Unfortunately for our elven friends, they appear to be screwed.  But what was that I said about messing with the space-time continuum earlier?  It turns out we can give them some help by upgrading their weaponry for Part 2 of the problem.  We need to figure out the minimal weapon upgrade we can provide that allows the elves to leave the battle triumphant while not suffering a single casualty.  To do so, we are going to have to go an additional level deeper with our Macro-ception by nesting our battle macros (round/turn/path) inside yet a FOURTH iterative macro!

 

InceptionDeeperGIF.gif

 

This will allow us to iteratively increase the elves’ attack power by 1 until we reach our desired victory conditions:

 

Hub119_20-1764010599311.png

 

 

It should be noted that each of the embedded macros should also be slightly tweaked for this part of the problem in order to make them halt as soon as a single elven unit dies (no need to let the entire battle play out in those cases).  At this point we can finally hit Run, sit back, and wait (admittedly for a while) to allow a number of battle simulations to play out as we continuously tinker with our elven arsenal until they achieve their victory without any unit losses.  Phew, that was a lot of work just to fight over some hot chocolate!

 

via GIPHY

 

While we are on the topic of role-playing adventures, remember how I mentioned in the last post that there were some unique things you get to build with IntCode programs in AoC 2019?  If you make it to the final day (now using an ASCII input enabled version of your IntCode “computer”), the provided input program (puzzle input) actually allows you to play what amounts to a text based adventure game as you use it to communicate back and forth with a droid navigating through Santa’s spaceship lost and freezing in de... (I told you there were storylines to these things).  Once you have it built you can either have some fun and just play through it until you find the locked door at the end and figure out what needs to be done to open it, or you can build in some functionality to test all movement and item pick up permutations until the objective of the “quest” is achieved and the program returns the answer you need to submit to earn your final star.  Either way, a very fun way to close out that year’s AoC.  And if you think that’s cool, now that we’re all sidetracked, maybe you should go check out another Obscura creation: Project Asparagus for a unique take on an Alteryx text based role playing game/choose your own adventure.  Actually, while we’re on this tangent… now that I’m thinking about it, perhaps if we combined those more advanced combat (like equipment choices and spell casting) and map movement mechanics with some troop placement, and added some interface tools for interactivity instead of just automating the game play, I just might have my own turn-based tactical RPG Obscura project idea to develop here… now that I am out of old AoC problems to solve… 🤔

 

-----

 

The various examples spread across this blog series are quite literally only the tip of the iceberg of what Advent of Code has to offer.  If any of these things sounded remotely interesting (hopefully the case if you have made it this far), it's not too late to join in on the fun yourself!  Be sure to check out AOC 2025 and join your fellow AYXxAoC compatriots this December for the 11th edition of Advent of Code to find out what interesting problems are in store for all of us this year!

 

Hub119_22-1764010599340.png

 

 

Only 12 days of problems this year 😢 – sad for veterans, but maybe great news for any AoC newbs?  Hope to have you all join us this month for the fun (madness?), and in the meantime, happy solving!

Comments