01020304050607080910111213141516171819202122232425

Advent of Code

2018/22

Mode Maze

in C#

by encse

This is it, your final stop: the year -483. It's snowing and dark outside; the only light you can see is coming from a small cottage in the distance. You make your way there and knock on the door.

A portly man with a large, white beard answers the door and invites you inside. For someone living near the North Pole in -483, he must not get many visitors, but he doesn't act surprised to see you. Instead, he offers you some milk and cookies.

Read the full puzzle.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;

namespace AdventOfCode.Y2018.Day22;

[ProblemName("Mode Maze")]
class Solution : Solver {

    public object PartOne(string input) {
        var (targetX, targetY, regionType) = Parse(input);
        var riskLevel = 0;
        for (var y = 0; y <= targetY; y++) {
            for (var x = 0; x <= targetX; x++) {
                riskLevel += (int)regionType(x, y);
            }
        }
        return riskLevel;
    }


    public object PartTwo(string input) {
        var (targetX, targetY, regionType) = Parse(input);
        var q = new PQueue<((int x, int y) pos, Tool tool, int t)>();
        var seen = new HashSet<((int x, int y), Tool tool)>();

        IEnumerable<((int x, int y) pos, Tool tool, int dt)> Neighbours((int x, int y) pos, Tool tool) {
            yield return regionType(pos.x, pos.y) switch {
                RegionType.Rocky => (pos, tool == Tool.ClimbingGear ? Tool.Torch : Tool.ClimbingGear, 7),
                RegionType.Narrow => (pos, tool == Tool.Torch ? Tool.Nothing : Tool.Torch, 7),
                RegionType.Wet => (pos, tool == Tool.ClimbingGear ? Tool.Nothing : Tool.ClimbingGear, 7),
                _ => throw new ArgumentException()
            };

            foreach (var dx in new[] { -1, 0, 1 }) {
                foreach (var dy in new[] { -1, 0, 1 }) {
                    if (Math.Abs(dx) + Math.Abs(dy) != 1) {
                        continue;
                    }

                    var posNew = (x: pos.x + dx, y: pos.y + dy);
                    if (posNew.x < 0 || posNew.y < 0) {
                        continue;
                    }

                    switch (regionType(posNew.x, posNew.y)) {
                        case RegionType.Rocky when tool == Tool.ClimbingGear || tool == Tool.Torch:
                        case RegionType.Narrow when tool == Tool.Torch || tool == Tool.Nothing:
                        case RegionType.Wet when tool == Tool.ClimbingGear || tool == Tool.Nothing:
                            yield return (posNew, tool, 1);
                            break;
                    }
                }
            }
        }

        q.Enqueue(0, ((0, 0), Tool.Torch, 0));

        while (q.Any()) {
            var state = q.Dequeue();
            var (pos, tool, t) = state;

            if (pos.x == targetX && pos.y == targetY && tool == Tool.Torch) {
                return t;
            }

            var hash = (pos, tool);
            if (seen.Contains(hash)) {
                continue;
            }

            seen.Add(hash);

            foreach( var (newPos, newTool, dt) in Neighbours(pos, tool)) {
                q.Enqueue(
                    t + dt + Math.Abs(newPos.x - targetX) + Math.Abs(newPos.y - targetY), 
                    (newPos, newTool, t + dt)
                );
            }

        }

        throw new Exception();
    }

    (int targetX, int targetY, Func<int, int, RegionType> regionType) Parse(string input) {
        var lines = input.Split("\n");
        var depth = Regex.Matches(lines[0], @"\d+").Select(x => int.Parse(x.Value)).Single();
        var target = Regex.Matches(lines[1], @"\d+").Select(x => int.Parse(x.Value)).ToArray();
        var (targetX, targetY) = (target[0], target[1]);

        var m = 20183;

        var erosionLevelCache = new Dictionary<(int, int), int>();
        int erosionLevel(int x, int y) {
            var key = (x, y);
            if (!erosionLevelCache.ContainsKey(key)) {
                if (x == targetX && y == targetY) {
                    erosionLevelCache[key] = depth;
                } else if (x == 0 && y == 0) {
                    erosionLevelCache[key] = depth;
                } else if (x == 0) {
                    erosionLevelCache[key] = ((y * 48271) + depth) % m;
                } else if (y == 0) {
                    erosionLevelCache[key] = ((x * 16807) + depth) % m;
                } else {
                    erosionLevelCache[key] = ((erosionLevel(x, y - 1) * erosionLevel(x - 1, y)) + depth) % m;
                }
            }
            return erosionLevelCache[key];
        }

        RegionType regionType(int x, int y) {
            return (RegionType)(erosionLevel(x, y) % 3);
        }

        return (targetX, targetY, regionType);
    }
}
enum RegionType {
    Rocky = 0,
    Wet = 1,
    Narrow = 2
}

enum Tool {
    Nothing,
    Torch,
    ClimbingGear
}

class PQueue<T> {
    SortedDictionary<int, Queue<T>> d = new SortedDictionary<int, Queue<T>>();
    public bool Any() {
        return d.Any();
    }

    public void Enqueue(int p, T t) {
        if (!d.ContainsKey(p)) {
            d[p] = new Queue<T>();
        }
        d[p].Enqueue(t);
    }

    public T Dequeue() {
        var p = d.Keys.First();
        var items = d[p];
        var t = items.Dequeue();
        if (!items.Any()) {
            d.Remove(p);
        }
        return t;
    }
}

Please ☆ my repo if you like it!

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