Hero image

Advent of Code 2025 - Puzzle 1


Advent of Code is a yearly puzzle that runs through December where each day a new puzzle is released that gets progressively harder. The goal is to take the text input and create an algorithm in any language to solve the puzzle.

For this year I decided to make my solutions into interactive widgets on the web.

Spoilers

WARNING - Below is a spoiler. If you want to figure it out yourself have a look at the puzzle on Advent of Code and come back after you have solved it.

Spoilers below. Scroll down at your own risk.

.

.

.

The Puzzle

This puzzle gets us to imagine a rotary combination lock that goes from 0 to 99. An algorithm has to be created that will allow the rotation of the lock to be processed from a series of instructions. The final part is to count the number of times the lock lands on 0 at the end of a turn.

Input

The instructions are provided as a series of left L or right R turns, and a number of ticks to turn.

R20
R40
L10

Solution

Parsing the instructions

The easiest part of this problem is to parse the input into something usable. There are two parts required:

  1. Splitting the input into lines.
  2. Extracting the direction and number of ticks from each line.

To split the input, it is simply using javascripts inbuild split function on strings. Then doing some basic parsing of the input to make it usable.

// Gives us individual instructions as elements in an array
const instructions = input.split("\n");

// Simple regex to require a line starting with L or R
// followed by at least one number and no other characters
const validLineRegex = /^[LR]\d+$/;

// Do basic parsing to only accept valid instructions
const validInstructions = instructions
    .map((line) => line.trim())
    .filter(line => line.length > 0  && validLineRegex.test(line));

After this we have an array of strings that represent valid instructions. Anything else has been removed from the input and is ignored.

Processing the instructions

The hardest part of this problem is to understand that the lock wraps itself when it goes past 0 or 99. This means that we can keep a simple sum of the current position without caring if it goes over or under the min and max value. All we need to do is use modulo to find out where we are on any divisor of 100.

For example if we are at position 95 and turn right 10 ticks we end up at 105. It does not matter that the value is outside the bounds of the lock, as we can just do 105 % 100 to get 5 as the final position.

Using this as a core concept, my full solution is below.

/*
 * Processes a single instruction and returns the new value
 */
function processInstruction(currentValue, instruction) {
    const direction = instruction.charAt(0);
    const ticks = parseInt(instruction.slice(1), 10);
    return direction === "R" ? currentValue + ticks : currentValue - ticks;
}

/*
 * Processes all instructions and returns the count of times the lock lands on 0
 */ 
function countZerosForInstructionSet(instructions) {
    let currentValue = 50;
    let zeroCount = 0;

    for (const instruction of instructions) {
        // It doesn't matter that `currentvalue` will go negative or over 100
        currentValue = processInstruction(currentValue, instruction);
        currentValue = currentValue % 100;

        if (currentValue === 0) {
            zeroCount++;
        }
    }

    return zeroCount;
}

Showing the Result

This solution was used to make the animation below. Play with the inputs to see how it works!

I have added a little lightning effect any time the condition is hit to make it easier to see when it happens.

0 10 20 30 40 50 60 70 80 90

Zero Count: 0


Part 2

Part 2 is all about crossing 0 rather than landing on it. It is similar enough that we can reuse most of the code from part 1 with a few changes. Primarily we do a calculation on the before and after value to see if we crossed 0 during the turn, and how many times we did so in a single turn.

function getNumberOfTimesZeroIsCrossed(from, to, multiple) {
  // Default calculation for multiples of 100
  // eg. from = 250, to = 50 means that:
  // fromMultiple = 2, toMultiple = 0, diff = 2
  const fromMultiple = Math.floor(from / multiple);
  const toMultiple = Math.floor(to / multiple);
  const diff = Math.abs(toMultiple - fromMultiple);

  // Special handling for starting on zero
  // eg. start on 100 and turn left 1 would give a diff of 1
  // but it should be 0 as we didn't cross zero
  const fromIsZero = from % 100 === 0;
  const fromIsOnZero = fromIsZero && to < from ? -1 : 0;

  // Special handling for landing on zero
  // eg. start on 1 and land on 0 would give a diff of 0
  // but it should be 1 as we did cross zero
  const toIsZero = to % 100 === 0;
  const shouldBumpTo = toIsZero && to < from ? 1 : 0;

  return diff + fromIsOnZero + shouldBumpTo;
};
0 10 20 30 40 50 60 70 80 90

Zero Count: 0



Want to read more? Check out more posts below!