mirror of
https://github.com/Threnklyn/advent-of-code-go.git
synced 2026-05-18 19:13:27 +02:00
465 lines
12 KiB
Go
465 lines
12 KiB
Go
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))
|
|
}
|