-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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
Showing
7 changed files
with
247 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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**? |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 }); | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
}; | ||
}); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |