Files
aoc2024/day15/main.go
2024-12-20 15:33:16 +01:00

272 lines
5.7 KiB
Go

package main
import (
"bufio"
"fmt"
"os"
"strings"
)
func main() {
{
exampleFloor := readFloor("example_map.txt")
exampleMoves, _ := os.ReadFile("example_moves.txt")
fmt.Println(part1(exampleFloor, exampleMoves), 10092)
}
{
dataFloor := readFloor("data_map.txt")
dataMoves, _ := os.ReadFile("data_moves.txt")
fmt.Println(part1(dataFloor, dataMoves), 1492518)
}
// {
// var simpleFloor1 [][]byte
// simpleFloor1 = append(simpleFloor1, []byte("##########"))
// simpleFloor1 = append(simpleFloor1, []byte("#........#"))
// simpleFloor1 = append(simpleFloor1, []byte("#........#"))
// simpleFloor1 = append(simpleFloor1, []byte("#.....O..#"))
// simpleFloor1 = append(simpleFloor1, []byte("#.....O..#"))
// simpleFloor1 = append(simpleFloor1, []byte("#.....O..#"))
// simpleFloor1 = append(simpleFloor1, []byte("#.....@..#"))
// simpleFloor1 = append(simpleFloor1, []byte("#........#"))
// simpleFloor1 = append(simpleFloor1, []byte("#........#"))
// simpleFloor1 = append(simpleFloor1, []byte("##########"))
// simpleFloor := makeWide(simpleFloor1)
// simpleMoves := []byte("^^")
// part2(simpleFloor, simpleMoves)
// }
{
exampleFloor := makeWide(readFloor("example_map.txt"))
exampleMoves, _ := os.ReadFile("example_moves.txt")
fmt.Println(part2(exampleFloor, exampleMoves), 9021)
}
{
dataFloor := makeWide(readFloor("data_map.txt"))
dataMoves, _ := os.ReadFile("data_moves.txt")
fmt.Println(part2(dataFloor, dataMoves))
}
}
func part1(floor [][]byte, moves []byte) (result int) {
botx, boty := findInitialPosition(floor)
doMoves(moves, botx, boty, floor, doMovePart1)
for y, line := range floor {
for x, c := range line {
if c == 'O' {
result += y*100 + x
}
}
}
return
}
func doMovePart1(floor [][]byte, startx, starty int, dx, dy int) (endx, endy int) {
x := startx
y := starty
moved := 0
for {
moved += 1
x += dx
y += dy
if floor[y][x] == '#' {
return startx, starty
}
if floor[y][x] == '.' {
if moved > 1 {
floor[y][x] = 'O'
}
floor[starty+dy][startx+dx] = '@'
floor[starty][startx] = '.'
return startx + dx, starty + dy
}
}
}
func doMoves(moves []byte, botx int, boty int, floor [][]byte, doMoveFn func([][]byte, int, int, int, int) (int, int)) {
for _, move := range moves {
switch move {
case '^':
botx, boty = doMoveFn(floor, botx, boty, 0, -1)
case 'v':
botx, boty = doMoveFn(floor, botx, boty, 0, 1)
case '<':
botx, boty = doMoveFn(floor, botx, boty, -1, 0)
case '>':
botx, boty = doMoveFn(floor, botx, boty, 1, 0)
}
}
}
func findInitialPosition(floor [][]byte) (int, int) {
var botx, boty int
for y, line := range floor {
idx := strings.IndexByte(string(line), '@')
if idx >= 0 {
botx = idx
boty = y
break
}
}
return botx, boty
}
type QueuedMove struct {
x, y int
c byte
}
func part2(floor [][]byte, moves []byte) (result int) {
var botx, boty int
for y, line := range floor {
idx := strings.IndexByte(string(line), '@')
if idx >= 0 {
botx = idx
boty = y
break
}
}
for _, move := range moves {
var queued []QueuedMove
var dx, dy int
switch move {
case '^':
dx, dy = 0, -1
case 'v':
dx, dy = 0, 1
case '<':
dx, dy = -1, 0
case '>':
dx, dy = 1, 0
default:
continue
}
if doMovePart2(floor, botx+dx, boty+dy, dx, dy, &queued) {
hasMadeMove := make(map[QueuedMove]bool)
for i := range queued {
m := queued[len(queued)-1-i]
_, alreadyMade := hasMadeMove[m]
if !alreadyMade {
floor[m.y][m.x] = m.c
floor[m.y-dy][m.x-dx] = '.'
hasMadeMove[m] = true
}
}
floor[boty][botx] = '.'
botx += dx
boty += dy
floor[boty][botx] = '@'
}
if !checkIntegrity(floor) {
panic("no")
}
}
for y, line := range floor {
for x, c := range line {
if c == '[' {
result += y*100 + x
}
}
}
return
}
func printMap(floor [][]byte) {
for _, line := range floor {
fmt.Println(string(line))
}
}
func checkIntegrity(floor [][]byte) bool {
for _, line := range floor {
for x, c := range line {
if c == '[' && line[x+1] != ']' {
return false
}
}
}
return true
}
func enqueueMove(x, y int, c byte, queued *[]QueuedMove) {
*queued = append(*queued, QueuedMove{x, y, c})
}
func doMovePart2(floor [][]byte, x, y int, dx, dy int, queued *[]QueuedMove) bool {
switch floor[y][x] {
case '.':
return true
case '#':
return false
case '[':
if dx == 0 {
enqueueMove(x, y+dy, '[', queued)
enqueueMove(x+1, y+dy, ']', queued)
return doMovePart2(floor, x, y+dy, dx, dy, queued) && doMovePart2(floor, x+1, y+dy, dx, dy, queued)
} else if dx > 0 {
enqueueMove(x+1, y, '[', queued)
enqueueMove(x+2, y, ']', queued)
return doMovePart2(floor, x+2, y, dx, dy, queued)
}
case ']':
if dx == 0 {
enqueueMove(x-1, y+dy, '[', queued)
enqueueMove(x, y+dy, ']', queued)
return doMovePart2(floor, x-1, y+dy, dx, dy, queued) && doMovePart2(floor, x, y+dy, dx, dy, queued)
} else if dx < 0 {
enqueueMove(x-1, y, ']', queued)
enqueueMove(x-2, y, '[', queued)
return doMovePart2(floor, x-2, y, dx, dy, queued)
}
}
return false
}
func makeWide(floor [][]byte) (wide [][]byte) {
wide = make([][]byte, len(floor))
for y, line := range floor {
w := make([]byte, len(line)*2)
for i, c := range line {
switch c {
case '#':
w[i*2+0] = '#'
w[i*2+1] = '#'
case '.':
w[i*2+0] = '.'
w[i*2+1] = '.'
case 'O':
w[i*2+0] = '['
w[i*2+1] = ']'
case '@':
w[i*2+0] = '@'
w[i*2+1] = '.'
}
}
wide[y] = w
}
return
}
func readFloor(fileName string) (floor [][]byte) {
fp, _ := os.Open(fileName)
scanner := bufio.NewScanner(fp)
for scanner.Scan() {
floor = append(floor, []byte(strings.TrimSpace(scanner.Text())))
}
return
}