01020304050607080910111213141516171819202122232425

Advent of Code

2024/6

Guard Gallivant

in C#

by encse

The Historians use their fancy device again, this time to whisk you all away to the North Pole prototype suit manufacturing lab... in the year 1518! It turns out that having direct access to history is very convenient for a group of historians.

You still have to be careful of time paradoxes, and so it will be important to avoid anyone from 1518 while The Historians search for the Chief. Unfortunately, a single guard is patrolling this part of the lab.

Visit the website for the full story and puzzle description.

This has been a straightforward implementation challenge. I wrote a Walk function that tracks the guard's movement and returns the visited locations. It also determines whether the guard enters a loop or exits the grid. Part1 utilizes only the location information, while Part2 adds blockers along the guard's path and counts the instances where he starts walking in a cycle.

To make a 90º turn in 2D you need swap the coordinates and multiply one of them by -1. The turn can be clockwise or counterclockwise, it depends on which coordinate was multiplied.

Here we use complex numbers to represent coordinates, and we get the same effect by simply multiplying with ImaginaryOne or i. -i turns right, and i to left (but this depends on how you draw your coordinate system of course, 'i' points upwards in mine).

It's not complicated at all, but if sounds a bit magical to You, try it out on a few vectors by hand.

namespace AdventOfCode.Y2024.Day06;

using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Numerics;
using Map = System.Collections.Immutable.ImmutableDictionary<System.Numerics.Complex, char>;

[ProblemName("Guard Gallivant")]
class Solution : Solver {

    Complex Up = Complex.ImaginaryOne;
    Complex TurnRight = -Complex.ImaginaryOne;

    public object PartOne(string input) {
        var (map, start) = Parse(input);
        return Walk(map, start).positions.Count();
    }

    public object PartTwo(string input) {
        var (map, start) = Parse(input);
        // try a blocker in each locations visited by the guard counting the loops
        return Walk(map, start).positions
                .AsParallel()
                .Count(pos => Walk(map.SetItem(pos, '#'), start).isLoop);
       
    }

    // returns the positions visited when starting from 'pos', isLoop is set if the 
    // guard enters a cycle.
    (IEnumerable<Complex> positions, bool isLoop) Walk(Map map, Complex pos) {
        var seen = new HashSet<(Complex pos, Complex dir)>();
        var dir = Up;
        while (map.ContainsKey(pos) && !seen.Contains((pos, dir))) {
            seen.Add((pos, dir));
            if (map.GetValueOrDefault(pos + dir) == '#') {
                dir *= TurnRight;
            } else {
                pos += dir;
            }
        }
        return (
            positions: seen.Select(s => s.pos).Distinct(),
            isLoop: seen.Contains((pos, dir))
        );
    }

    // store the grid in a dictionary, to make bounds checks and navigation simple
    // start represents the starting postion of the guard
    (Map map, Complex start) Parse(string input) {
        var lines = input.Split("\n");
        var map = (
            from y in Enumerable.Range(0, lines.Length)
            from x in Enumerable.Range(0, lines[0].Length)
            select new KeyValuePair<Complex, char>(-Up * y + x, lines[y][x])
        ).ToImmutableDictionary();

        var start = map.First(x => x.Value == '^').Key;
        
        return (map, start);
    }
}

Please ☆ my repo if you like it!

© 2025 Advent of Code is a registered trademark in the US Images provided by Bing image creator