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