278 lines
6.5 KiB
Go
278 lines
6.5 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
|
|
"git.mstar.dev/mstar/aoc24/util"
|
|
)
|
|
|
|
type Area struct {
|
|
a [][]rune
|
|
}
|
|
|
|
func parseLinesToMap(lines []string) Area {
|
|
out := [][]rune{}
|
|
|
|
for _, line := range lines {
|
|
out = append(out, []rune(strings.TrimSpace(line)))
|
|
}
|
|
|
|
return Area{out}
|
|
}
|
|
|
|
func parseInput(in []byte) (Area, []rune) {
|
|
tmp := strings.Split(string(in), "\n\n")
|
|
return parseLinesToMap(strings.Split(tmp[0], "\n")), []rune(tmp[1])
|
|
}
|
|
|
|
func (area *Area) applyAction(action rune) {
|
|
botX := -1
|
|
botY := -1
|
|
outer:
|
|
for iy, line := range area.a {
|
|
for ix, char := range line {
|
|
if char == '@' {
|
|
botX = ix
|
|
botY = iy
|
|
break outer
|
|
}
|
|
}
|
|
}
|
|
area.move(util.Vec2{X: int64(botX), Y: int64(botY)}, action)
|
|
}
|
|
|
|
func (area *Area) move(from util.Vec2, action rune) bool {
|
|
switch action {
|
|
case '<':
|
|
return area.moveLeft(from)
|
|
case '>':
|
|
return area.moveRight(from)
|
|
case 'v':
|
|
return area.moveDown(from)
|
|
case '^':
|
|
return area.moveUp(from)
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
func (area *Area) moveLeft(from util.Vec2) bool {
|
|
target := area.a[from.Y][from.X-1]
|
|
switch target {
|
|
case '#':
|
|
return false
|
|
case '.':
|
|
area.a[from.Y][from.X-1] = area.a[from.Y][from.X]
|
|
area.a[from.Y][from.X] = '.'
|
|
return true
|
|
default:
|
|
return area.moveLeft(from.Add(util.Vec2{X: -1, Y: 0})) && area.moveLeft(from)
|
|
}
|
|
}
|
|
|
|
func (area *Area) moveRight(from util.Vec2) bool {
|
|
target := area.a[from.Y][from.X+1]
|
|
switch target {
|
|
case '#':
|
|
return false
|
|
case '.':
|
|
area.a[from.Y][from.X+1] = area.a[from.Y][from.X]
|
|
area.a[from.Y][from.X] = '.'
|
|
return true
|
|
default:
|
|
return area.moveRight(from.Add(util.Vec2{X: 1, Y: 0})) && area.moveRight(from)
|
|
}
|
|
}
|
|
|
|
func (area *Area) canMoveUp(at util.Vec2) bool {
|
|
// fmt.Println("Checking at", at)
|
|
if area.a[at.Y][at.X] == '#' {
|
|
return false
|
|
}
|
|
switch area.a[at.Y-1][at.X] {
|
|
case '#':
|
|
return false
|
|
case '.':
|
|
return true
|
|
case 'O':
|
|
return area.canMoveUp(at.Add(util.Vec2{X: 0, Y: -1}))
|
|
case '[':
|
|
return area.canMoveUp(at.Add(util.Vec2{X: 0, Y: -1})) &&
|
|
area.canMoveUp(at.Add(util.Vec2{X: 1, Y: -1}))
|
|
case ']':
|
|
return area.canMoveUp(at.Add(util.Vec2{X: 0, Y: -1})) &&
|
|
area.canMoveUp(at.Add(util.Vec2{X: -1, Y: -1}))
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
func (area *Area) canMoveDown(at util.Vec2) bool {
|
|
if area.a[at.Y][at.X] == '#' {
|
|
return false
|
|
}
|
|
switch area.a[at.Y+1][at.X] {
|
|
case '#':
|
|
return false
|
|
case '.':
|
|
return true
|
|
case 'O':
|
|
return area.canMoveDown(at.Add(util.Vec2{X: 0, Y: 1}))
|
|
case '[':
|
|
return area.canMoveDown(at.Add(util.Vec2{X: 0, Y: 1})) &&
|
|
area.canMoveDown(at.Add(util.Vec2{X: 1, Y: 1}))
|
|
case ']':
|
|
return area.canMoveDown(at.Add(util.Vec2{X: 0, Y: 1})) &&
|
|
area.canMoveDown(at.Add(util.Vec2{X: -1, Y: 1}))
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
func (area *Area) moveUp(from util.Vec2) bool {
|
|
// fmt.Printf("Moving up at %v (%s)\n", from, string(area.a[from.Y][from.X]))
|
|
target := area.a[from.Y-1][from.X]
|
|
switch target {
|
|
case '#':
|
|
// fmt.Println("Hit Wall")
|
|
return false
|
|
case '.':
|
|
// fmt.Printf("Empty, moving from %v to %v\n", from, from.Add(util.Vec2{X: 0, Y: -1}))
|
|
area.a[from.Y-1][from.X] = area.a[from.Y][from.X]
|
|
area.a[from.Y][from.X] = '.'
|
|
return true
|
|
case '[':
|
|
if !(area.canMoveUp(from.Add(util.Vec2{X: 0, Y: -1})) &&
|
|
area.canMoveUp(from.Add(util.Vec2{X: 1, Y: -1}))) {
|
|
return false
|
|
}
|
|
// fmt.Println("Moving Box left first")
|
|
return area.moveUp(from.Add(util.Vec2{X: 0, Y: -1})) &&
|
|
area.moveUp(from.Add(util.Vec2{X: 1, Y: -1})) &&
|
|
area.moveUp(from)
|
|
case ']':
|
|
// fmt.Println("Box right")
|
|
return area.canMoveUp(from.Add(util.Vec2{X: 0, Y: -1})) &&
|
|
area.canMoveUp(from.Add(util.Vec2{X: -1, Y: -1})) &&
|
|
area.moveUp(from.Add(util.Vec2{X: 0, Y: -1})) &&
|
|
area.moveUp(from.Add(util.Vec2{X: -1, Y: -1})) &&
|
|
area.moveUp(from)
|
|
case '@':
|
|
// fmt.Println("Moving bot")
|
|
return area.moveUp(from.Add(util.Vec2{X: 0, Y: -1})) && area.moveUp(from)
|
|
case 'O':
|
|
return area.moveUp(from.Add(util.Vec2{X: 0, Y: -1})) && area.moveUp(from)
|
|
default:
|
|
return area.moveUp(from.Add(util.Vec2{X: 0, Y: -1})) && area.moveUp(from)
|
|
}
|
|
}
|
|
|
|
func (area *Area) moveDown(from util.Vec2) bool {
|
|
// fmt.Printf("Moving down at %v\n", from)
|
|
target := area.a[from.Y+1][from.X]
|
|
switch target {
|
|
case '#':
|
|
return false
|
|
case '.':
|
|
area.a[from.Y+1][from.X] = area.a[from.Y][from.X]
|
|
area.a[from.Y][from.X] = '.'
|
|
return true
|
|
case '[':
|
|
return area.canMoveDown(from.Add(util.Vec2{X: 0, Y: 1})) &&
|
|
area.canMoveDown(from.Add(util.Vec2{X: 1, Y: 1})) &&
|
|
area.moveDown(from.Add(util.Vec2{X: 0, Y: 1})) &&
|
|
area.moveDown(from.Add(util.Vec2{X: 1, Y: 1})) &&
|
|
area.moveDown(from)
|
|
case ']':
|
|
return area.canMoveDown(from.Add(util.Vec2{X: 0, Y: 1})) &&
|
|
area.canMoveDown(from.Add(util.Vec2{X: -1, Y: 1})) &&
|
|
area.moveDown(from.Add(util.Vec2{X: 0, Y: 1})) &&
|
|
area.moveDown(from.Add(util.Vec2{X: -1, Y: 1})) &&
|
|
area.moveDown(from)
|
|
default:
|
|
return area.moveDown(from.Add(util.Vec2{X: 0, Y: 1})) && area.moveDown(from)
|
|
}
|
|
}
|
|
|
|
func (area *Area) visualise() string {
|
|
builder := strings.Builder{}
|
|
for _, line := range area.a {
|
|
builder.WriteString(string(line))
|
|
builder.WriteRune('\n')
|
|
}
|
|
|
|
return builder.String()
|
|
}
|
|
|
|
func (area *Area) calcPosScore() int {
|
|
score := 0
|
|
for iy, line := range area.a {
|
|
for ix, char := range line {
|
|
if char == 'O' || char == '[' {
|
|
score += calcPosVal(ix, iy)
|
|
}
|
|
}
|
|
}
|
|
return score
|
|
}
|
|
|
|
func Widen(in string) string {
|
|
return strings.ReplaceAll(
|
|
strings.ReplaceAll(
|
|
strings.ReplaceAll(strings.ReplaceAll(in, ".", ".."), "@", "@."),
|
|
"#",
|
|
"##",
|
|
),
|
|
"O",
|
|
"[]",
|
|
)
|
|
}
|
|
|
|
func (area *Area) confirmBoxes(step int) {
|
|
for iy, line := range area.a {
|
|
lastWasBoxLeft := false
|
|
for ix, char := range line {
|
|
if lastWasBoxLeft && char != ']' {
|
|
fmt.Println(area.visualise())
|
|
panic(fmt.Sprintf("Broken box right missing at %d %d, step %d", ix, iy, step))
|
|
}
|
|
switch char {
|
|
case '[':
|
|
lastWasBoxLeft = true
|
|
default:
|
|
lastWasBoxLeft = false
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func calcPosVal(x, y int) int {
|
|
return x + y*100
|
|
}
|
|
|
|
func main() {
|
|
rawInput := util.LoadFileFromArgs()
|
|
area, inputs := parseInput(rawInput)
|
|
// fmt.Println(area.visualise())
|
|
for _, action := range inputs {
|
|
// fmt.Println(string(action))
|
|
area.applyAction(action)
|
|
// fmt.Println(area.visualise())
|
|
}
|
|
fmt.Printf("Task 1: %d\n", area.calcPosScore())
|
|
|
|
wArea, _ := parseInput([]byte(Widen(string(rawInput))))
|
|
fmt.Println(wArea.visualise())
|
|
for i, action := range inputs {
|
|
fmt.Println(string(action))
|
|
wArea.applyAction(action)
|
|
// if i == 806 {
|
|
// fmt.Println(wArea.visualise())
|
|
// }
|
|
wArea.confirmBoxes(i)
|
|
fmt.Println(wArea.visualise())
|
|
}
|
|
fmt.Println(wArea.visualise())
|
|
fmt.Printf("Task 2: %d\n", wArea.calcPosScore())
|
|
}
|