Skip to content

Commit

Permalink
Add solution for 2016, day 1
Browse files Browse the repository at this point in the history
Part 1 was quickly done but part 2 gave me some trouble. Understanding
the challenge was the first hurdle, it took a while before I
understood what was meant by visited location. This is not just the
points you end up after following a direction but also any location
in between.

Once the challenge was clear a small modification to the solution for
part 1 was enough to get all the points from where we travelled into
a new direction. This made it possible to create line segments and to
find two intersecting line segments.
  • Loading branch information
tbusser committed Nov 30, 2024
1 parent 01bd721 commit a314e60
Show file tree
Hide file tree
Showing 7 changed files with 247 additions and 0 deletions.
26 changes: 26 additions & 0 deletions 2016/01/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Day 1: No Time for a Taxicab

Santa's sleigh uses a very high-precision clock to guide its movements, and the clock's oscillator is regulated by stars. Unfortunately, the stars have been stolen... by the Easter Bunny. To save Christmas, Santa needs you to retrieve all *fifty stars* by December 25th.

Collect stars by solving puzzles. Two puzzles will be made available on each day in the Advent calendar; the second puzzle is unlocked when you complete the first. Each puzzle grants *one star*. Good luck!

You're airdropped near **Easter Bunny Headquarters** in a city somewhere. "Near", unfortunately, is as close as you can get - the instructions on the Easter Bunny Recruiting Document the Elves intercepted start here, and nobody had time to work them out further.

The Document indicates that you should start at the given coordinates (where you just landed) and face North. Then, follow the provided sequence: either turn left (`L) or right (`R`) 90 degrees, then walk forward the given number of blocks, ending at a new intersection.

There's no time to follow such ridiculous instructions on foot, though, so you take a moment and work out the destination. Given that you can only walk on the **street grid of the city**, how far is the shortest path to the destination?

For example:

- Following `R2, L3` leaves you `2` blocks East and `3` blocks North, or `5` blocks away.
- `R2, R2, R2` leaves you `2` blocks due South of your starting position, which is `2` blocks away.
- `R5, L5, R5, R3` leaves you `12` blocks away.

How many blocks away is Easter Bunny HQ?

## Part Two
Then, you notice the instructions continue on the back of the Recruiting Document. Easter Bunny HQ is actually at the first location you visit twice.

For example, if your instructions are `R8, R4, R4, R8`, the first location you visit twice is 4 blocks away, due East.

How many blocks away is the **first location you visit twice**?
10 changes: 10 additions & 0 deletions 2016/01/helpers/follow-directions.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { describe, expect, it } from 'vitest';
import { followRoute } from './follow-directions.js';

describe('2016/1', () => {
describe('followRoute', () => {
it('should follow the set of directions', () => {
expect(followRoute([{ steps: 2, turn: -1 }])).toEqual({ x: -2, y: 0 });
});
});
});
43 changes: 43 additions & 0 deletions 2016/01/helpers/follow-directions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { Coordinate, Instruction } from './types.js';

/* ========================================================================== */

/**
* The array contains the instructions on what to do for each direction. The
* order in the array is: north, east, south, and west.
*
* The value is made up as follow:
* - The ones digit is the horizontal movement. A positive number means a move
* to the east, a negative number is a move to the west.
* - The tens digit is the vertical movement. A positive number means a move to
* the north, a negative number a move to the south.
*/
const directions = [10, 1, -10, -1];

/* ========================================================================== */

function determineNewDirection(index: number, turn: number): number {
index += turn;

if (index < 0) return 3;
if (index > 3) return 0;

return index;
}

/* -------------------------------------------------------------------------- */

export function followRoute(route: Instruction[]): Coordinate[] {
let direction = 0;
const position: Coordinate = { x: 0, y: 0 };
const path: Coordinate[] = [{ ...position }];

for (const { steps, turn } of route) {
direction = determineNewDirection(direction, turn);
position.x += (directions[direction] % 10) * steps;
position.y += Math.round(directions[direction] / 10) * steps;
path.push({ ...position });
}

return path;
}
17 changes: 17 additions & 0 deletions 2016/01/helpers/parse-input.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { Instruction } from './types.js';

/* ========================================================================== */

export function parseInput(input: string): Instruction[] {
const lines = input.split(', ');

return lines.map(line => {
const turn = line.charAt(0) as 'R' | 'L';
const steps = Number(line.substring(1));

return {
steps,
turn: turn === 'R' ? 1 : -1
};
});
}
17 changes: 17 additions & 0 deletions 2016/01/helpers/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
export type Coordinate = {
x: number;
y: number;
};

export type Instruction = {
/**
* The number of steps to take in the new direction.
*/
steps: number;

/**
* When turn is 1 it means to turn 90 degrees clockwise. When it is set to
* -1 it indicates to make a 90 degrees turn counterclockwise.
*/
turn: 1 | -1;
};
19 changes: 19 additions & 0 deletions 2016/01/part-1.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { followRoute } from './helpers/follow-directions.js';
import { parseInput } from './helpers/parse-input.js';

/* ========================================================================== */

async function solver(input: string): Promise<number> {
const route = parseInput(input);
const path = followRoute(route);
const endPoint = path.at(-1);

return Math.abs(endPoint.x) + Math.abs(endPoint.y);
}

/* ========================================================================== */

export default {
prompt: 'Number of blocks away from Easter Bunny HQ',
solver
} satisfies Solution;
115 changes: 115 additions & 0 deletions 2016/01/part-2.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import { Coordinate } from '@helpers/grid.js';
import { followRoute } from './helpers/follow-directions.js';
import { parseInput } from './helpers/parse-input.js';

/* ========================================================================== */

type Segment = {
end: Coordinate;
orientation: 'horizontal' | 'vertical';
start: Coordinate;
};

/* ========================================================================== */

function findIntersectingPoint(segmentA: Segment, segmentB: Segment): Coordinate | null {
const horizontalSegment = segmentA.orientation === 'horizontal'
? segmentA : segmentB;
const verticalSegment = segmentA.orientation === 'horizontal'
? segmentB : segmentA;

if (
horizontalSegment.start.y <= verticalSegment.start.y ||
horizontalSegment.start.y >= verticalSegment.end.y
) {
return null;
}

if (
verticalSegment.start.x <= horizontalSegment.start.x ||
verticalSegment.start.x >= horizontalSegment.end.x
) {
return null;
}

return {
x: verticalSegment.start.x,
y: horizontalSegment.start.y
};
}

/**
* Creates line segments for the the path. All horizontal line segments will run
* from west to east and all the vertical line segments will run from south to
* north. Making sure all lines run in the same direction makes it easier later
* to determine if lines are intersecting.
*/
function createLineSegments(path: Coordinate[]): Segment[] {
const segments: Segment[] = [];

// Iterate over the points and create a line segment between the current
// point and the next point.
for (let index = 0; index < path.length - 1; index++) {
const pointA = path[index];
const pointB = path[index + 1];
// Check if the line segment is horizontal or vertical.
const orientation = (pointA.x === pointB.x) ? 'vertical' : 'horizontal';

if (orientation === 'horizontal') {
// Order the points so the horizontal segment always goes from west
// to east.
segments.push({
end: pointA.x > pointB.x ? pointA : pointB,
orientation,
start: pointA.x > pointB.x ? pointB : pointA,
});
} else {
// Order the points so the vertical segment always goes from south
// to north.
segments.push({
end: pointA.y > pointB.y ? pointA : pointB,
orientation,
start: pointA.y > pointB.y ? pointB : pointA,
});
}
}

return segments;
}

/* -------------------------------------------------------------------------- */

async function solver(input: string): Promise<number> {
const route = parseInput(input);
const path = followRoute(route);
const segments = createLineSegments(path);

// The fourth line segment is the first one that can intersect with a
// previous line segment (line segment 4 can intersect with line segment 4
// if the first 4 turns are all in the same direction). For that reason
// start at index 3 (the 4th item).
for (let index = 3; index < segments.length; index++) {
// The current segment can only intersect with a line segment which runs
// perpendicular. To only check the perpendicular lines we need to start
// at index 0 or 1 depending on the current index.
// E.g.: If the current index is 6, the first perpendicular line segment
// is at index 1 as all even indexes will run in the same direction.
let perpendicularIndex = index % 2 === 0 ? 1 : 0;
// Skip every other index so we only test the perpendicular lines.
for (; perpendicularIndex < index; perpendicularIndex = perpendicularIndex + 2) {
const intersectingPoint = findIntersectingPoint(segments[index], segments[perpendicularIndex]);
if (intersectingPoint === null) continue;

return Math.abs(intersectingPoint.x) + Math.abs(intersectingPoint.y);
}
}

return -1;
}

/* ========================================================================== */

export default {
prompt: 'Distance to the first block visited twice',
solver
} satisfies Solution;

0 comments on commit a314e60

Please sign in to comment.