Files
advent-of-code-go/2022/day22/main.go
T
alexchao26 419485f35a wow
2022-12-28 00:51:30 -05:00

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))
}