DEV Community

Discussion on: Advent of Code 2020 Solution Megathread - Day 12: Rain Risk

Collapse
mgasparel profile image
Mike Gasparelli

Warning, lots of code today! I've been making an effort to model my solutions in ways that do not require special branching, or separate solutions for the 2 parts. Today was a fun one, and I think I came up with a good design, even if it's a little verbose!

At this point I think my enterprise development roots are showing. I've got inheritance, interfaces, strategies, factories, and even a DI container in my overall 2020 codebase 🤣

Part1

    public class Part1 : Puzzle<IEnumerable<NavInstruction>, int>
    {
        public override int SampleAnswer => 25;

        public override IEnumerable<NavInstruction> ParseInput(string rawInput)
            => rawInput
                .Split(Environment.NewLine)
                .Where(line => line.Length > 0)
                .Select(line =>
                    new NavInstruction(
                        Action: line[0],
                        Value: int.Parse(line[1..])
                    ));

        public override int Solve(IEnumerable<NavInstruction> input)
        {
            var origin = new Point(0, 0);
            var ferry = new Ferry(origin);
            ferry.Sail(input);
            return ManhattanDistance(origin, ferry.Location);
        }

        protected static int ManhattanDistance(Point a, Point b)
            => Math.Abs(a.X - b.X) + Math.Abs(a.Y - b.Y);
    }
Enter fullscreen mode Exit fullscreen mode

Part 2

    public class Part2 : Part1
    {
        public override int SampleAnswer => 286;

        public override int Solve(IEnumerable<NavInstruction> input)
        {
            var origin = new Point(0, 0);
            var ferry = new WaypointFerry(origin, new Point(10, 1));
            ferry.Sail(input);
            return ManhattanDistance(origin, ferry.Location);
        }
    }
Enter fullscreen mode Exit fullscreen mode

FerryBase

public abstract class FerryBase
    {
        public Point Location { get; protected set; }

        public Direction Bearing { get; protected set; }

        protected FerryBase(Point origin)
        {
            Location = origin;
            Bearing = Direction.Right;
        }

        public void Sail(IEnumerable<NavInstruction> instructions)
        {
            foreach (NavInstruction instruction in instructions)
            {
                Move(instruction);
            }
        }

        protected Direction GetDirection(NavInstruction instruction)
            => instruction.Action switch {
                'N' => Direction.Up,
                'S' => Direction.Down,
                'E' => Direction.Right,
                'W' => Direction.Left,
                _ => Bearing
            };

        protected static Point Move(Point p, Direction direction, int value)
            => direction switch {
                Direction.Up => new Point(p.X, p.Y + value),
                Direction.Down => new Point(p.X, p.Y - value),
                Direction.Left => new Point(p.X - value, p.Y),
                Direction.Right => new Point(p.X + value, p.Y),
                _ => p
            };

        protected void Rotate(char turnDirection, int angle)
        {
            for (int i = 0; i < angle / 90; i++)
            {
                Rotate(turnDirection);
            }
        }

        protected abstract void Rotate(char turnDirection);

        protected abstract void Move(NavInstruction instruction);
    }
Enter fullscreen mode Exit fullscreen mode

Ferry (Part1's Implementation)

public class Ferry : FerryBase
    {
        public Ferry(Point origin)
            : base(origin)
        {
        }

        protected override void Rotate(char turnDirection)
        {
            int iBearing = (int)Bearing;
            Bearing = turnDirection switch {
                'L' => (Direction)((iBearing + 3) % 4),
                'R' => (Direction)((iBearing + 1) % 4),
                _ => Bearing
            };
        }

        protected override void Move(NavInstruction instruction)
        {
            if (instruction.Action is 'L' or 'R')
            {
                Rotate(instruction.Action, instruction.Value);
                return;
            }

            Move(GetDirection(instruction), instruction.Value);
        }

        void Move(Direction direction, int value)
            => Location = Move(Location, direction, value);
    }
Enter fullscreen mode Exit fullscreen mode

WaypointFerry (Part2's Implementation)

public class WaypointFerry : FerryBase
    {
        Point Waypoint = new Point(0, 0);

        public WaypointFerry(Point origin, Point waypoint)
            : base(origin)
        {
            Waypoint = waypoint;
        }

        protected override void Move(NavInstruction instruction)
        {
            if (instruction.Action is 'L' or 'R')
            {
                Rotate(instruction.Action, instruction.Value);
                return;
            }

            if (instruction.Action is 'F')
            {
                for (int i = 0; i < instruction.Value; i++)
                {
                    Location = new Point(Location.X + Waypoint.X, Location.Y + Waypoint.Y);
                }

                return;
            }

            Waypoint = Move(Waypoint, GetDirection(instruction), instruction.Value);
        }

        protected override void Rotate(char turnDirection)
            => Waypoint = turnDirection switch {
                'L' => new Point(-1 * Waypoint.Y, Waypoint.X),
                'R' => new Point(Waypoint.Y, -1 * Waypoint.X),
                _ => throw new Exception($"turnDirection not supported: {turnDirection}")
            };
    }
Enter fullscreen mode Exit fullscreen mode