Watch
1
0
Fork
You've already forked advent-of-code
0
advent-of-code/2021/day23/main.go
2021-12-24 03:28:38 -08:00

485 lines
8.3 KiB
Go

package main
import (
"container/heap"
"fmt"
"strings"
"github.com/jdkaplan/advent-of-code/aoc"
)
func main() {
startText := aoc.Input().ReadFile("day23-part2.txt")
w, start := parseInput(startText)
fmt.Println(part1(w, start))
}
func parseInput(text string) (Walls, State) {
w := make(Walls)
pods := make(map[Object][]RC)
for r, line := range strings.Split(text, "\n") {
for c, char := range line {
if char == '.' || char == ' ' {
continue
}
rc := RC{r, c}
obj := parseObject(char)
if obj == Wall {
w[rc] = true
}
pods[obj] = append(pods[obj], rc)
}
}
as := pods[Amber]
bs := pods[Bronze]
cs := pods[Copper]
ds := pods[Desert]
if len(as) != podCount {
panic(as)
}
if len(bs) != podCount {
panic(bs)
}
if len(cs) != podCount {
panic(cs)
}
if len(ds) != podCount {
panic(ds)
}
s := State{
A: [podCount]RC{as[0], as[1], as[2], as[3]},
B: [podCount]RC{bs[0], bs[1], bs[2], bs[3]},
C: [podCount]RC{cs[0], cs[1], cs[2], cs[3]},
D: [podCount]RC{ds[0], ds[1], ds[2], ds[3]},
}
return w, s
}
func part1(w Walls, start State) int {
successors := func(s State) map[State]int {
fmt.Println(s.Debug(w))
return s.Successors(w)
}
return search(start, State.Goal, successors, State.Wrong)
}
type Object rune
const (
Empty Object = '.'
Wall Object = '#'
Amber Object = 'A'
Bronze Object = 'B'
Copper Object = 'C'
Desert Object = 'D'
)
func parseObject(r rune) Object {
switch r {
case '#':
return Wall
case '.':
return Empty
case 'A':
return Amber
case 'B':
return Bronze
case 'C':
return Copper
case 'D':
return Desert
}
panic(fmt.Sprintf("Unknown object: %s", string(r)))
}
type RC struct{ r, c int }
func (rc RC) Neighbors() []RC {
r, c := rc.r, rc.c
return []RC{
{r - 1, c},
{r, c - 1},
{r, c + 1},
{r + 1, c},
}
}
const podCount = 4
const rHallway = 1
const rMax = rHallway + podCount
type Walls map[RC]bool
func (w Walls) IsDoor(rc RC) bool {
if rc.r != rHallway {
return false
}
switch rc.c {
case 3, 5, 7, 9:
return true
}
return false
}
func (w Walls) Blocked(rc RC) bool {
r, c := rc.r, rc.c
ok := rHallway <= r && r <= rMax && 1 <= c && c <= 9
wall := w[rc]
return ok && !wall
}
type State struct{ A, B, C, D [podCount]RC }
func (s State) pods() map[RC]Object {
p := make(map[RC]Object)
for _, a := range s.A {
p[a] = Amber
}
for _, b := range s.B {
p[b] = Bronze
}
for _, c := range s.C {
p[c] = Copper
}
for _, d := range s.D {
p[d] = Desert
}
return p
}
func (s State) Debug(w Walls) string {
pods := s.pods()
var sb strings.Builder
for r := 0; r <= rMax+1; r++ {
for c := 0; c <= 12; c++ {
rc := RC{r, c}
if w[rc] {
sb.WriteByte('#')
} else if pod, ok := pods[rc]; ok {
sb.WriteRune(rune(pod))
} else {
sb.WriteByte(' ')
}
}
sb.WriteByte('\n')
}
return sb.String()
}
func (s State) Successors(w Walls) map[State]int {
type Out struct {
state State
cost int
}
var outs []Out
for i, a := range s.A {
for aa, cost := range podMoves(w, s, a, Amber) {
n := s
n.A[i] = aa
outs = append(outs, Out{n, cost})
}
}
for i, b := range s.B {
for bb, cost := range podMoves(w, s, b, Bronze) {
n := s
n.B[i] = bb
outs = append(outs, Out{n, cost})
}
}
for i, c := range s.C {
for cc, cost := range podMoves(w, s, c, Copper) {
n := s
n.C[i] = cc
outs = append(outs, Out{n, cost})
}
}
for i, d := range s.D {
for dd, cost := range podMoves(w, s, d, Desert) {
n := s
n.D[i] = dd
outs = append(outs, Out{n, cost})
}
}
next := make(map[State]int)
for _, out := range outs {
min, ok := next[out.state]
if !ok || out.cost < min {
next[out.state] = out.cost
}
}
return next
}
func (s State) Goal() bool {
for _, a := range s.A {
if a.c != 3 {
return false
}
}
for _, b := range s.B {
if b.c != 5 {
return false
}
}
for _, c := range s.C {
if c.c != 7 {
return false
}
}
for _, d := range s.D {
if d.c != 9 {
return false
}
}
return true
}
func (s State) Wrong() (cost int) {
for _, a := range s.A {
if a.c != 3 {
cost += 2
}
}
for _, b := range s.B {
if b.c != 5 {
cost += 20
}
}
for _, c := range s.C {
if c.c != 7 {
cost += 200
}
}
for _, d := range s.D {
if d.c != 9 {
cost += 2000
}
}
return
}
func podMoves(w Walls, s State, start RC, kind Object) (costs map[RC]int) {
var targetC, cost int
switch kind {
case Amber:
targetC, cost = 3, 1
case Bronze:
targetC, cost = 5, 10
case Copper:
targetC, cost = 7, 100
case Desert:
targetC, cost = 9, 1000
default:
panic(":grimace:")
}
mode := podMode(w, s, start, kind, targetC)
return podPath(w, s, start, mode, targetC, cost)
}
type Mode string
const (
Exit Mode = "exit"
Wait Mode = "wait"
Park Mode = "park"
Done Mode = "done"
)
func podMode(w Walls, s State, rc RC, kind Object, targetC int) Mode {
pods := s.pods()
if rc.r == rHallway {
// In the hallway. Would another pod need to leave?
for r := rc.r + 1; r <= rMax; r++ {
inner := RC{r, targetC}
if k, ok := pods[inner]; ok && k != kind {
// Need to let the other pod out.
return Wait
}
}
// Nope, get moving!
return Park
}
if rc.c == targetC {
// In correct column. Does another pod want to leave?
for r := rc.r + 1; r <= rMax; r++ {
inner := RC{r, targetC}
if k, ok := pods[inner]; ok && k != kind {
// Need to let the other pod out.
return Exit
}
}
// No reason to leave!
return Done
}
return Exit
}
func podPath(w Walls, s State, rc RC, mode Mode, targetC int, cost int) (costs map[RC]int) {
switch mode {
case Exit:
// Any walkable space in the hallway that's not the door.
costs = make(map[RC]int)
for _, spot := range walk(w, s, rc) {
if spot.r == rHallway && !w.IsDoor(spot) {
costs[spot] = cost * manhattan(rc, spot)
}
}
return costs
case Wait:
// Do nothing
return nil
case Park:
// Only the correct parking spot
var bestSpot RC
for _, spot := range walk(w, s, rc) {
if spot.c != targetC || w.IsDoor(spot) {
continue
}
// Prefer inner (downward, higher-row) spot
if spot.r > bestSpot.r {
bestSpot = spot
}
}
if bestSpot == (RC{}) {
// No available spots
return nil
}
return map[RC]int{
bestSpot: cost * manhattan(rc, bestSpot),
}
case Done:
// Nothing
return nil
default:
panic("oops")
}
}
func manhattan(a, b RC) int {
return abs(a.r-b.r) + abs(a.c-b.c)
}
func abs(x int) int {
if x < 0 {
return -x
}
return x
}
func walk(w Walls, s State, from RC) (spots []RC) {
queue := []RC{from}
seen := make(map[RC]bool)
pods := s.pods()
var rc RC
for len(queue) > 0 {
rc, queue = queue[0], queue[1:]
if seen[rc] {
continue
}
for _, n := range rc.Neighbors() {
isWall := w[n]
_, isFull := pods[n]
if isWall || isFull {
continue
}
queue = append(queue, n)
}
seen[rc] = true
}
for spot := range seen {
spots = append(spots, spot)
}
return spots
}
func search(
start State,
isGoal func(State) bool,
successors func(State) map[State]int,
heuristic func(State) int,
) (totalCost int) {
if heuristic == nil {
heuristic = func(State) int { return 0 }
}
pq := make(PriorityQueue, 1)
pq[0] = &Node{
s: start,
cost: 0,
heur: 0,
idx: 0,
}
heap.Init(&pq)
seen := make(map[State]bool)
for len(pq) > 0 {
state, cost := pq.Next()
if isGoal(state) {
return cost
}
if seen[state] {
continue
}
for s, c := range successors(state) {
pq.Insert(s, cost+c, heuristic(state))
}
seen[state] = true
}
return -1
}
type Node struct {
s State
cost int
heur int
idx int
}
type PriorityQueue []*Node
func (pq *PriorityQueue) Next() (s State, cost int) {
n := heap.Pop(pq).(*Node)
return n.s, n.cost
}
func (pq *PriorityQueue) Insert(s State, cost int, heur int) {
heap.Push(pq, &Node{s: s, cost: cost, heur: heur})
}
func (pq PriorityQueue) Len() int {
return len(pq)
}
func (pq PriorityQueue) Less(i, j int) bool {
ii := pq[i].cost + pq[i].heur
jj := pq[j].cost + pq[j].heur
return ii < jj
}
func (p PriorityQueue) Swap(i, j int) {
p[i], p[j] = p[j], p[i]
p[i].idx = i
p[j].idx = j
}
func (pq *PriorityQueue) Push(x interface{}) {
n := len(*pq)
node := x.(*Node)
node.idx = n
*pq = append(*pq, node)
}
func (pq *PriorityQueue) Pop() interface{} {
old := *pq
n := len(old)
node := old[n-1]
old[n-1] = nil
node.idx = -1
*pq = old[0 : n-1]
return node
}