mirror of
https://github.com/Threnklyn/advent-of-code-go.git
synced 2026-06-07 12:45:10 +02:00
wow
This commit is contained in:
@@ -0,0 +1,464 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"flag"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/alexchao26/advent-of-code-go/cast"
|
||||
"github.com/alexchao26/advent-of-code-go/util"
|
||||
)
|
||||
|
||||
//go:embed input.txt
|
||||
var input string
|
||||
|
||||
func init() {
|
||||
// do this in init (not main) so test file has same input
|
||||
input = strings.TrimRight(input, "\n")
|
||||
if len(input) == 0 {
|
||||
panic("empty input.txt file")
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
var part int
|
||||
flag.IntVar(&part, "part", 1, "part 1 or 2")
|
||||
flag.Parse()
|
||||
fmt.Println("Running part", part)
|
||||
|
||||
if part == 1 {
|
||||
ans := part1(input)
|
||||
util.CopyToClipboard(fmt.Sprintf("%v", ans))
|
||||
fmt.Println("Output:", ans)
|
||||
} else {
|
||||
ans := part2(input)
|
||||
util.CopyToClipboard(fmt.Sprintf("%v", ans))
|
||||
fmt.Println("Output:", ans)
|
||||
}
|
||||
}
|
||||
|
||||
func part1(input string) int {
|
||||
matrix, path := parseInput(input)
|
||||
var row, col int
|
||||
for c := 0; c < len(matrix[0]); c++ {
|
||||
if matrix[0][c] == "." {
|
||||
col = c
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
diffIndex := 0
|
||||
diffs := [][2]int{
|
||||
{0, 1}, // start facing right
|
||||
{1, 0}, // turning right will point you down
|
||||
{0, -1}, // left
|
||||
{-1, 0}, // turning left from index 0 makes you face up
|
||||
}
|
||||
|
||||
// walking over the edge wraps you around within the same direction...
|
||||
// unless if it is a wall, then you're stuck
|
||||
|
||||
p := 0
|
||||
for p < len(path) {
|
||||
// get next direction...
|
||||
indexOfLorR := p
|
||||
for indexOfLorR < len(path) &&
|
||||
path[indexOfLorR] != 'L' && path[indexOfLorR] != 'R' {
|
||||
indexOfLorR++
|
||||
}
|
||||
steps := cast.ToInt(path[p:indexOfLorR])
|
||||
// try to move that many steps
|
||||
for s := 0; s < steps; s++ {
|
||||
diff := diffs[diffIndex]
|
||||
nextRow, nextCol := row+diff[0], col+diff[1]
|
||||
// mod them so they wrap if necessary
|
||||
nextRow += len(matrix)
|
||||
nextCol += len(matrix[0])
|
||||
|
||||
nextRow %= len(matrix)
|
||||
nextCol %= len(matrix[0])
|
||||
|
||||
// if it's an empty space you need to keep looping around...
|
||||
for matrix[nextRow][nextCol] == " " || matrix[nextRow][nextCol] == "" {
|
||||
nextRow += diff[0]
|
||||
nextCol += diff[1]
|
||||
|
||||
// wrapping math...
|
||||
nextRow += len(matrix)
|
||||
nextCol += len(matrix[0])
|
||||
|
||||
nextRow %= len(matrix)
|
||||
nextCol %= len(matrix[0])
|
||||
}
|
||||
|
||||
// wall: break
|
||||
if matrix[nextRow][nextCol] == "#" {
|
||||
break
|
||||
}
|
||||
row = nextRow
|
||||
col = nextCol
|
||||
}
|
||||
|
||||
if indexOfLorR == len(path) {
|
||||
break
|
||||
}
|
||||
// handle turn if indexOfLorR is still in bounds
|
||||
switch path[indexOfLorR] {
|
||||
case 'L':
|
||||
diffIndex--
|
||||
case 'R':
|
||||
diffIndex++
|
||||
}
|
||||
diffIndex += 4
|
||||
diffIndex %= 4
|
||||
p = indexOfLorR + 1
|
||||
}
|
||||
|
||||
// final row, col, facing
|
||||
// row & col are 1 indexed
|
||||
// facing is indexed same as diffs slice
|
||||
// 1000 * row + 4 * col + facing_index
|
||||
return 1000*(row+1) + 4*(col+1) + diffIndex
|
||||
}
|
||||
|
||||
func parseInput(input string) ([][]string, string) {
|
||||
parts := strings.Split(input, "\n\n")
|
||||
|
||||
matrix := [][]string{}
|
||||
topRowLen := len(strings.Split(parts[0], "\n")[0])
|
||||
|
||||
for _, line := range strings.Split(parts[0], "\n") {
|
||||
matrix = append(matrix, make([]string, topRowLen))
|
||||
split := strings.Split(line, "")
|
||||
copy(matrix[len(matrix)-1], split)
|
||||
}
|
||||
|
||||
return matrix, parts[1]
|
||||
}
|
||||
|
||||
func part2(input string) int {
|
||||
matrix, path := parseInput(input)
|
||||
var row, col int
|
||||
for c := 0; c < len(matrix[0]); c++ {
|
||||
if matrix[0][c] == "." {
|
||||
col = c
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
diffIndex := 0
|
||||
diffs := [][2]int{
|
||||
{0, 1}, // start facing right
|
||||
{1, 0}, // turning right will point you DOWN
|
||||
{0, -1}, // left
|
||||
{-1, 0}, // turning left from index 0 makes you face up
|
||||
}
|
||||
|
||||
// shape of example
|
||||
// #
|
||||
// ###
|
||||
// ##
|
||||
//
|
||||
//
|
||||
// shape of my input
|
||||
// ##
|
||||
// #
|
||||
// ##
|
||||
// #
|
||||
|
||||
// a lot of (hah) edge case handling to determine where to "teleport" to
|
||||
// pen and paper math... might not be worth doing the example input
|
||||
// because i'm going to make a literal calculation for my input shape...
|
||||
|
||||
p := 0
|
||||
for p < len(path) {
|
||||
// get next direction...
|
||||
indexOfLorR := p
|
||||
for indexOfLorR < len(path) &&
|
||||
path[indexOfLorR] != 'L' && path[indexOfLorR] != 'R' {
|
||||
indexOfLorR++
|
||||
}
|
||||
steps := cast.ToInt(path[p:indexOfLorR])
|
||||
|
||||
// try to move that many steps
|
||||
for s := 0; s < steps; s++ {
|
||||
diff := diffs[diffIndex]
|
||||
nextRow, nextCol := row+diff[0], col+diff[1]
|
||||
|
||||
// DO NOT UPDATE diffIndex here because if it's a wall we DON'T want
|
||||
// to change directions
|
||||
nextRow, nextCol, nextDiffIndex := handleWrap(row, col, nextRow, nextCol, diffIndex)
|
||||
|
||||
// we'll never see empty spaces now because we're handling wrapping above
|
||||
// wall: break
|
||||
if matrix[nextRow][nextCol] == "#" {
|
||||
break
|
||||
}
|
||||
// only update if we didn't hit a wall
|
||||
row = nextRow
|
||||
col = nextCol
|
||||
diffIndex = nextDiffIndex
|
||||
}
|
||||
|
||||
if indexOfLorR == len(path) {
|
||||
break
|
||||
}
|
||||
// handle turn if indexOfLorR is still in bounds
|
||||
switch path[indexOfLorR] {
|
||||
case 'L':
|
||||
diffIndex--
|
||||
case 'R':
|
||||
diffIndex++
|
||||
}
|
||||
diffIndex += 4
|
||||
diffIndex %= 4
|
||||
p = indexOfLorR + 1
|
||||
}
|
||||
|
||||
// final answer calculated from flattened map coords
|
||||
// 1000 * row + 4 * col + facing_index
|
||||
// too low: 111043
|
||||
return 1000*(row+1) + 4*(col+1) + diffIndex
|
||||
}
|
||||
|
||||
// handles edge cases ;)
|
||||
// how i'll number my boxes...
|
||||
// 21
|
||||
// 3
|
||||
// 54
|
||||
// 6
|
||||
|
||||
const (
|
||||
RightIndex = 0
|
||||
DownIndex = 1
|
||||
LeftIndex = 2
|
||||
UpIndex = 3
|
||||
)
|
||||
|
||||
// handleWrap checks if the movement from r,c to nextRow, nextCol is off the
|
||||
// edge of the matrix, if so it does the maths and direction change to wrap
|
||||
// around the edge of the cube, this is very manual and based upon a drawing i
|
||||
// made of my input (i'll upload it when i remember...)
|
||||
//
|
||||
// got a little carried away with assertions in here trying to debug...
|
||||
func handleWrap(r, c, nextRow, nextCol, diffIndex int) (newRow, newCol, newDiffIndex int) {
|
||||
// there will be roughly 14 checks in here... this is gonna get ugly, esp
|
||||
// b/c i'm too lazy to dry this up
|
||||
|
||||
// 2 -> 5 conversion
|
||||
if getBoxNumber(r, c) == 2 &&
|
||||
0 <= nextRow && nextRow < 50 && nextCol == 49 {
|
||||
if diffIndex != LeftIndex {
|
||||
panic(fmt.Sprintf("expected LeftIndex, got %d", diffIndex))
|
||||
}
|
||||
|
||||
newCol = 0
|
||||
newRow = 149 - nextRow
|
||||
if getBoxNumber(newRow, newCol) != 5 {
|
||||
panic(fmt.Sprintf("expected to move to box 5, got %d", getBoxNumber(newRow, newCol)))
|
||||
}
|
||||
return newRow, newCol, RightIndex
|
||||
}
|
||||
// 5 -> 2
|
||||
if getBoxNumber(r, c) == 5 &&
|
||||
nextCol == -1 && 100 <= nextRow && nextRow < 150 {
|
||||
if diffIndex != LeftIndex {
|
||||
panic(fmt.Sprintf("expected LeftIndex got %d", diffIndex))
|
||||
}
|
||||
newCol = 50
|
||||
newRow = 149 - nextRow
|
||||
if getBoxNumber(newRow, newCol) != 2 {
|
||||
panic(fmt.Sprintf("expected to move to box 2, got %d", getBoxNumber(newRow, newCol)))
|
||||
}
|
||||
return newRow, newCol, RightIndex
|
||||
}
|
||||
|
||||
// 3 -> 5
|
||||
if getBoxNumber(r, c) == 3 &&
|
||||
nextCol == 49 && 50 <= nextRow && nextRow < 100 {
|
||||
if diffIndex != LeftIndex {
|
||||
panic(fmt.Sprintf("expected LeftIndex, got %d", diffIndex))
|
||||
}
|
||||
newRow = 100
|
||||
newCol = nextRow - 50
|
||||
if getBoxNumber(newRow, newCol) != 5 {
|
||||
panic(fmt.Sprintf("expected to move to box 5, got %d", getBoxNumber(newRow, newCol)))
|
||||
}
|
||||
return newRow, newCol, DownIndex
|
||||
}
|
||||
// 5 -> 3
|
||||
if getBoxNumber(r, c) == 5 &&
|
||||
nextRow == 99 && 0 <= nextCol && nextCol < 50 {
|
||||
if diffIndex != UpIndex {
|
||||
panic(fmt.Sprintf("expected UpIndex, got %d", diffIndex))
|
||||
}
|
||||
newRow = nextCol + 50
|
||||
newCol = 50
|
||||
if getBoxNumber(newRow, newCol) != 3 {
|
||||
panic(fmt.Sprintf("expected to move to box 3, got %d", getBoxNumber(newRow, newCol)))
|
||||
}
|
||||
return newRow, newCol, RightIndex
|
||||
}
|
||||
|
||||
// 2 -> 6
|
||||
if getBoxNumber(r, c) == 2 &&
|
||||
nextRow == -1 && 50 <= nextCol && nextCol < 100 {
|
||||
if diffIndex != UpIndex {
|
||||
panic(fmt.Sprintf("expected UpIndex, got %d", diffIndex))
|
||||
}
|
||||
newRow = nextCol + 100
|
||||
newCol = 0
|
||||
if getBoxNumber(newRow, newCol) != 6 {
|
||||
panic(fmt.Sprintf("expected to move to box 6, got %d", getBoxNumber(newRow, newCol)))
|
||||
}
|
||||
return newRow, newCol, RightIndex
|
||||
}
|
||||
// 6 -> 2
|
||||
if getBoxNumber(r, c) == 6 &&
|
||||
nextCol == -1 && 150 <= nextRow && nextRow < 200 {
|
||||
if diffIndex != LeftIndex {
|
||||
panic(fmt.Sprintf("expected LeftIndex, got %d", diffIndex))
|
||||
}
|
||||
newRow = 0
|
||||
newCol = nextRow - 100
|
||||
if getBoxNumber(newRow, newCol) != 2 {
|
||||
panic(fmt.Sprintf("expected to move to box 2, got %d", getBoxNumber(newRow, newCol)))
|
||||
}
|
||||
return newRow, newCol, DownIndex
|
||||
}
|
||||
|
||||
// 1 -> 6
|
||||
if getBoxNumber(r, c) == 1 &&
|
||||
nextRow == -1 && 100 <= nextCol && nextCol < 150 {
|
||||
if diffIndex != UpIndex {
|
||||
panic(fmt.Sprintf("expected UpIndex, got %d", diffIndex))
|
||||
}
|
||||
newRow = 199
|
||||
newCol = nextCol - 100
|
||||
if getBoxNumber(newRow, newCol) != 6 {
|
||||
panic(fmt.Sprintf("expected to move to box 6, got %d", getBoxNumber(newRow, newCol)))
|
||||
}
|
||||
return newRow, newCol, UpIndex
|
||||
}
|
||||
// 6 -> 1
|
||||
if getBoxNumber(r, c) == 6 &&
|
||||
nextRow == 200 && 0 <= nextCol && nextCol < 50 {
|
||||
if diffIndex != DownIndex {
|
||||
panic(fmt.Sprintf("expected DownIndex, got %d", diffIndex))
|
||||
}
|
||||
newRow = 0
|
||||
newCol = nextCol + 100
|
||||
if getBoxNumber(newRow, newCol) != 1 {
|
||||
panic(fmt.Sprintf("expected to move to box 1, got %d", getBoxNumber(newRow, newCol)))
|
||||
}
|
||||
return newRow, newCol, DownIndex
|
||||
}
|
||||
|
||||
// 4 -> 6
|
||||
if getBoxNumber(r, c) == 4 &&
|
||||
nextRow == 150 && 50 <= nextCol && nextCol < 100 {
|
||||
if diffIndex != DownIndex {
|
||||
panic(fmt.Sprintf("expected DownIndex, got %d", diffIndex))
|
||||
}
|
||||
newRow = nextCol + 100
|
||||
newCol = 49
|
||||
if getBoxNumber(newRow, newCol) != 6 {
|
||||
panic(fmt.Sprintf("expected to move to box 6, got %d", getBoxNumber(newRow, newCol)))
|
||||
}
|
||||
return newRow, newCol, LeftIndex
|
||||
}
|
||||
// 6 -> 4
|
||||
if getBoxNumber(r, c) == 6 &&
|
||||
nextCol == 50 && 150 <= nextRow && nextRow < 200 {
|
||||
if diffIndex != RightIndex {
|
||||
panic(fmt.Sprintf("expected RightIndex, got %d", diffIndex))
|
||||
}
|
||||
newRow = 149
|
||||
newCol = nextRow - 100
|
||||
if getBoxNumber(newRow, newCol) != 4 {
|
||||
panic(fmt.Sprintf("expected to move to box 4, got %d", getBoxNumber(newRow, newCol)))
|
||||
}
|
||||
return newRow, newCol, UpIndex
|
||||
}
|
||||
|
||||
// 4 -> 1
|
||||
if getBoxNumber(r, c) == 4 &&
|
||||
nextCol == 100 && 100 <= nextRow && nextRow < 150 {
|
||||
if diffIndex != RightIndex {
|
||||
panic(fmt.Sprintf("expected RightIndex, got %d", diffIndex))
|
||||
}
|
||||
newRow = 149 - nextRow
|
||||
newCol = 149
|
||||
if getBoxNumber(newRow, newCol) != 1 {
|
||||
panic(fmt.Sprintf("expected to move to box 1, got %d", getBoxNumber(newRow, newCol)))
|
||||
}
|
||||
return newRow, newCol, LeftIndex
|
||||
}
|
||||
// 1 -> 4
|
||||
if getBoxNumber(r, c) == 1 &&
|
||||
nextCol == 150 && 0 <= nextRow && nextRow < 50 {
|
||||
if diffIndex != RightIndex {
|
||||
panic(fmt.Sprintf("expected RightIndex, got %d", diffIndex))
|
||||
}
|
||||
newRow = 149 - nextRow
|
||||
newCol = 99
|
||||
if getBoxNumber(newRow, newCol) != 4 {
|
||||
panic(fmt.Sprintf("expected to move to box 4, got %d", getBoxNumber(newRow, newCol)))
|
||||
}
|
||||
return newRow, newCol, LeftIndex
|
||||
}
|
||||
|
||||
// 3 -> 1
|
||||
if getBoxNumber(r, c) == 3 &&
|
||||
nextCol == 100 && 50 <= nextRow && nextRow < 100 {
|
||||
if diffIndex != RightIndex {
|
||||
panic(fmt.Sprintf("expected RightIndex, got %d", diffIndex))
|
||||
}
|
||||
newRow = 49
|
||||
newCol = nextRow + 50
|
||||
if getBoxNumber(newRow, newCol) != 1 {
|
||||
panic(fmt.Sprintf("expected to move to box 1, got %d", getBoxNumber(newRow, newCol)))
|
||||
}
|
||||
return newRow, newCol, UpIndex
|
||||
}
|
||||
// 1 -> 3
|
||||
if getBoxNumber(r, c) == 1 &&
|
||||
nextRow == 50 && 100 <= nextCol && nextCol < 150 {
|
||||
if diffIndex != DownIndex {
|
||||
panic(fmt.Sprintf("expected DownIndex, got %d", diffIndex))
|
||||
}
|
||||
newRow = nextCol - 50
|
||||
newCol = 99
|
||||
if getBoxNumber(newRow, newCol) != 3 {
|
||||
panic(fmt.Sprintf("expected to move to box 3, got %d", getBoxNumber(newRow, newCol)))
|
||||
}
|
||||
return newRow, newCol, LeftIndex
|
||||
}
|
||||
|
||||
// no edge conversion required, just pass through
|
||||
return nextRow, nextCol, diffIndex
|
||||
}
|
||||
|
||||
func getBoxNumber(r, c int) int {
|
||||
if 0 <= r && r < 50 && 100 <= c && c < 150 {
|
||||
return 1
|
||||
}
|
||||
if 0 <= r && r < 50 && 50 <= c && c < 100 {
|
||||
return 2
|
||||
}
|
||||
if 50 <= r && r < 100 && 50 <= c && c < 100 {
|
||||
return 3
|
||||
}
|
||||
if 100 <= r && r < 150 && 50 <= c && c < 100 {
|
||||
return 4
|
||||
}
|
||||
if 100 <= r && r < 150 && 0 <= c && c < 50 {
|
||||
return 5
|
||||
}
|
||||
if 150 <= r && r < 200 && 0 <= c && c < 50 {
|
||||
return 6
|
||||
}
|
||||
|
||||
panic(fmt.Sprintf("bad row %d and col %d", r, c))
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
var example = ` ...#
|
||||
.#..
|
||||
#...
|
||||
....
|
||||
...#.......#
|
||||
........#...
|
||||
..#....#....
|
||||
..........#.
|
||||
...#....
|
||||
.....#..
|
||||
.#......
|
||||
......#.
|
||||
|
||||
10R5L5R10L4R5L5`
|
||||
|
||||
func Test_part1(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
want int
|
||||
}{
|
||||
{
|
||||
name: "example",
|
||||
input: example,
|
||||
want: 6032,
|
||||
},
|
||||
{
|
||||
name: "actual",
|
||||
input: input,
|
||||
want: 144244,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := part1(tt.input); got != tt.want {
|
||||
t.Errorf("part1() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_part2(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
want int
|
||||
}{
|
||||
// NOTE: did not account for the example case because I manually coded
|
||||
// for the shape of my exact input... which differed from the
|
||||
// input shape
|
||||
// {
|
||||
// name: "example",
|
||||
// input: example,
|
||||
// want: 5031,
|
||||
// },
|
||||
{
|
||||
name: "actual",
|
||||
input: input,
|
||||
want: 138131,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := part2(tt.input); got != tt.want {
|
||||
t.Errorf("part2() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user