2021 - Day 23
This commit is contained in:
parent
aaab01f178
commit
100d0ab60a
9
2021/23/go.mod
Normal file
9
2021/23/go.mod
Normal file
|
@ -0,0 +1,9 @@
|
|||
module AoC/2021/23
|
||||
|
||||
require AoC/2021/common v0.0.0
|
||||
|
||||
require github.com/joho/godotenv v1.4.0 // indirect
|
||||
|
||||
replace AoC/2021/common v0.0.0 => ../common
|
||||
|
||||
go 1.17
|
2
2021/23/go.sum
Normal file
2
2021/23/go.sum
Normal file
|
@ -0,0 +1,2 @@
|
|||
github.com/joho/godotenv v1.4.0 h1:3l4+N6zfMWnkbPEXKng2o2/MR5mSwTrBih4ZEkkz1lg=
|
||||
github.com/joho/godotenv v1.4.0/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
500
2021/23/main.go
Normal file
500
2021/23/main.go
Normal file
|
@ -0,0 +1,500 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
aoc "AoC/2021/common"
|
||||
"bufio"
|
||||
"fmt"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
type Amphipod int
|
||||
|
||||
const (
|
||||
Empty Amphipod = 0
|
||||
Amber = 1
|
||||
Bronze = 10
|
||||
Copper = 100
|
||||
Desert = 1000
|
||||
)
|
||||
|
||||
func GetAmphipod(s string) Amphipod {
|
||||
switch s {
|
||||
case "A":
|
||||
return Amber
|
||||
case "B":
|
||||
return Bronze
|
||||
case "C":
|
||||
return Copper
|
||||
case "D":
|
||||
return Desert
|
||||
default:
|
||||
panic("Unknown type of Amphipod")
|
||||
}
|
||||
}
|
||||
|
||||
func (a Amphipod) Letter() string {
|
||||
switch a {
|
||||
case Amber:
|
||||
return "A"
|
||||
case Bronze:
|
||||
return "B"
|
||||
case Copper:
|
||||
return "C"
|
||||
case Desert:
|
||||
return "D"
|
||||
default:
|
||||
return "."
|
||||
}
|
||||
}
|
||||
|
||||
func (a Amphipod) Destination() (int, error) {
|
||||
switch a {
|
||||
case Amber:
|
||||
return 0, nil
|
||||
case Bronze:
|
||||
return 1, nil
|
||||
case Copper:
|
||||
return 2, nil
|
||||
case Desert:
|
||||
return 3, nil
|
||||
}
|
||||
|
||||
return -1, fmt.Errorf("Amphipod has no destination")
|
||||
}
|
||||
|
||||
type Room struct {
|
||||
position int
|
||||
occupants [4]Amphipod
|
||||
}
|
||||
|
||||
func (r Room) ValidDesination(a Amphipod) (int, error) {
|
||||
depth := -1
|
||||
for i := range r.occupants {
|
||||
if r.occupants[i] == Empty {
|
||||
depth++
|
||||
} else if r.occupants[i] != a {
|
||||
return -1, fmt.Errorf("Invalid destination")
|
||||
}
|
||||
}
|
||||
|
||||
return depth, nil
|
||||
|
||||
}
|
||||
|
||||
func (r Room) InCorrectRoom(index int, a Amphipod) bool {
|
||||
b := Empty
|
||||
switch index {
|
||||
case 0:
|
||||
b = Amber
|
||||
case 1:
|
||||
b = Bronze
|
||||
case 2:
|
||||
b = Copper
|
||||
case 3:
|
||||
b = Desert
|
||||
default:
|
||||
return false
|
||||
}
|
||||
|
||||
return a == b
|
||||
}
|
||||
|
||||
func (r Room) IsAmphipodDone(index int, depth int, a Amphipod) bool {
|
||||
if !r.InCorrectRoom(index, a) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Check if the Amphipods below are correct
|
||||
for i := depth; i < len(r.occupants); i++ {
|
||||
if r.occupants[i] != a {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (r Room) Solved(index int) bool {
|
||||
for i := range r.occupants {
|
||||
if !r.InCorrectRoom(index, r.occupants[i]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (r Room) Top() (Amphipod, int) {
|
||||
for i := range r.occupants {
|
||||
if r.occupants[i] != Empty {
|
||||
return r.occupants[i], i
|
||||
}
|
||||
}
|
||||
|
||||
return r.occupants[0], 0
|
||||
}
|
||||
|
||||
// Create a new room with the top entry removed
|
||||
func (r Room) Pop() (Room, int) {
|
||||
for i := range r.occupants {
|
||||
if r.occupants[i] == Empty {
|
||||
continue
|
||||
}
|
||||
|
||||
r.occupants[i] = Empty
|
||||
return r, i
|
||||
}
|
||||
|
||||
return r, 0
|
||||
}
|
||||
|
||||
type Puzzle struct {
|
||||
corridor [11]Amphipod
|
||||
|
||||
rooms [4]Room
|
||||
}
|
||||
|
||||
func (p Puzzle) CheckCorridor(start int, end int) (bool, int) {
|
||||
cost := 0
|
||||
for k := start; k <= end; k++ {
|
||||
cost++
|
||||
if p.corridor[k] != Empty {
|
||||
// If there is an non empty tile along the way we move on to the next amphipod
|
||||
// We do not have to check the other rooms as there can only ever be at most one valid room
|
||||
return false, 0
|
||||
}
|
||||
}
|
||||
|
||||
return true, cost
|
||||
}
|
||||
|
||||
func (p Puzzle) Print() {
|
||||
fmt.Println("#############")
|
||||
fmt.Print("#")
|
||||
for _, a := range p.corridor {
|
||||
fmt.Printf("%s", a.Letter())
|
||||
}
|
||||
fmt.Print("#\n")
|
||||
|
||||
for i := 0; i < len(p.rooms[0].occupants); i++ {
|
||||
filler := " "
|
||||
if i == 0 {
|
||||
filler = "##"
|
||||
}
|
||||
fmt.Printf("%s", filler)
|
||||
for _, r := range p.rooms {
|
||||
fmt.Printf("#%s", r.occupants[i].Letter())
|
||||
}
|
||||
fmt.Printf("#%s\n", filler)
|
||||
}
|
||||
fmt.Println(" ######### ")
|
||||
}
|
||||
|
||||
func (p Puzzle) Solved() bool {
|
||||
for i := 0; i < len(p.rooms[0].occupants); i++ {
|
||||
if p.rooms[0].occupants[i] != Amber {
|
||||
return false
|
||||
}
|
||||
if p.rooms[1].occupants[i] != Bronze {
|
||||
return false
|
||||
}
|
||||
if p.rooms[2].occupants[i] != Copper {
|
||||
return false
|
||||
}
|
||||
if p.rooms[3].occupants[i] != Desert {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (p Puzzle) Solve(iteration int) (bool, int) {
|
||||
// First check if we have already calculated the state
|
||||
// if cost, ok := cache[p]; ok {
|
||||
// return cost
|
||||
// }
|
||||
|
||||
// fmt.Println("Depth:", iteration)
|
||||
// p.Print()
|
||||
|
||||
cost := 0
|
||||
|
||||
// If possible move Amphipod to destination room
|
||||
resolve:
|
||||
for true {
|
||||
// fmt.Println("Checking: ROOM => ROOM movement")
|
||||
for i, r := range p.rooms {
|
||||
// Check if the room is already solved
|
||||
if r.Solved(i) {
|
||||
continue
|
||||
}
|
||||
|
||||
a, adepth := r.Top()
|
||||
|
||||
if a == Empty {
|
||||
continue
|
||||
}
|
||||
|
||||
if r.IsAmphipodDone(i, adepth, a) {
|
||||
continue
|
||||
}
|
||||
|
||||
d, _ := a.Destination()
|
||||
if edepth, err := p.rooms[d].ValidDesination(a); err == nil {
|
||||
// The room is free and valid
|
||||
start := r.position
|
||||
end := p.rooms[d].position
|
||||
if start > end {
|
||||
temp := start
|
||||
start = end
|
||||
end = temp
|
||||
}
|
||||
free, potentialCost := p.CheckCorridor(start, end)
|
||||
if !free {
|
||||
continue
|
||||
}
|
||||
|
||||
// fmt.Println(potentialCost, r.position, p.rooms[d].position)
|
||||
|
||||
// fmt.Printf("\tMoving %v from %d to %d\n", a.Letter(), i, d)
|
||||
|
||||
potentialCost += edepth
|
||||
var sdepth int
|
||||
p.rooms[i], sdepth = r.Pop()
|
||||
p.rooms[d].occupants[edepth] = a
|
||||
potentialCost += sdepth
|
||||
potentialCost += 1
|
||||
|
||||
// fmt.Printf("\t\t%d %d\n", sdepth, edepth)
|
||||
|
||||
potentialCost *= int(a)
|
||||
|
||||
// fmt.Printf("\t\tCost: %d\n", potentialCost)
|
||||
|
||||
cost += potentialCost
|
||||
|
||||
continue resolve
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// fmt.Println("Checking: CORRIDOR => ROOM movement")
|
||||
// For every space in the corridor we check if there is a Amphipod that could move
|
||||
for i, a := range p.corridor {
|
||||
if a == Empty {
|
||||
continue
|
||||
}
|
||||
|
||||
// Get the ID of the destination room
|
||||
j, _ := a.Destination()
|
||||
|
||||
// Check if the destination room is free
|
||||
if depth, err := p.rooms[j].ValidDesination(a); err == nil {
|
||||
// Get the start and end position
|
||||
start := i+1
|
||||
end := p.rooms[j].position
|
||||
if start > end {
|
||||
temp := start-2
|
||||
start = end
|
||||
end = temp
|
||||
}
|
||||
|
||||
free, potentialCost := p.CheckCorridor(start, end)
|
||||
if !free {
|
||||
continue
|
||||
}
|
||||
// We need to add one since we start with a move to the left or the right
|
||||
potentialCost += depth + 1
|
||||
potentialCost *= int(a)
|
||||
|
||||
// fmt.Printf("\tMoving %s from %d to %d\n", a.Letter(), i, j)
|
||||
// fmt.Printf("\t\tCost: %d\n", potentialCost)
|
||||
|
||||
// Update state
|
||||
p.corridor[i] = Empty
|
||||
p.rooms[j].occupants[depth] = a
|
||||
|
||||
cost += potentialCost
|
||||
|
||||
// Restart looping over all spaces in the corridor as another amphipod might now be able to move
|
||||
continue resolve
|
||||
}
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
if p.Solved() {
|
||||
return true, cost
|
||||
}
|
||||
|
||||
// Generate future states
|
||||
minCost := -1
|
||||
for i, r := range p.rooms {
|
||||
// Check if the room is already solved
|
||||
if r.Solved(i) {
|
||||
continue
|
||||
}
|
||||
|
||||
// Get the top amphipod in the room
|
||||
a, adepth := r.Top()
|
||||
|
||||
// The room is empty, move on to the next room
|
||||
if a == Empty {
|
||||
continue
|
||||
}
|
||||
|
||||
if r.IsAmphipodDone(i, adepth, a) {
|
||||
continue
|
||||
}
|
||||
|
||||
// Move the amphipod to the corridor
|
||||
outer:
|
||||
for j, c := range p.corridor {
|
||||
// The amphipod can not stop in from of the room
|
||||
// We also can not stop in another amphipod
|
||||
if c != Empty {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, rr := range p.rooms {
|
||||
if j == rr.position {
|
||||
continue outer
|
||||
}
|
||||
}
|
||||
|
||||
// Get the start and end point
|
||||
start := r.position
|
||||
end := j
|
||||
if start > end {
|
||||
temp := start
|
||||
start = end
|
||||
end = temp
|
||||
}
|
||||
|
||||
free, potentialCost := p.CheckCorridor(start, end)
|
||||
if !free {
|
||||
continue
|
||||
}
|
||||
|
||||
// Create the new state
|
||||
np := p
|
||||
nr, depth := r.Pop()
|
||||
np.rooms[i] = nr
|
||||
np.corridor[j] = a
|
||||
|
||||
solved, nextCost := np.Solve(iteration+1)
|
||||
|
||||
if solved {
|
||||
nextCost += (potentialCost + depth) * int(a)
|
||||
|
||||
if nextCost < minCost || minCost == -1 {
|
||||
minCost = nextCost
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if minCost == -1 {
|
||||
return false, 10000000
|
||||
}
|
||||
|
||||
return true, cost + minCost
|
||||
}
|
||||
|
||||
func NewPuzzle1(input *bufio.Scanner) Puzzle {
|
||||
input.Scan()
|
||||
input.Scan()
|
||||
|
||||
r := regexp.MustCompile("[A-Z]")
|
||||
|
||||
var p Puzzle
|
||||
for i := 0; i < 2; i++ {
|
||||
input.Scan()
|
||||
line := r.FindAllString(input.Text(), 4)
|
||||
|
||||
for j, a := range line {
|
||||
p.rooms[j].occupants[i] = GetAmphipod(a)
|
||||
}
|
||||
}
|
||||
|
||||
// Pad the bottom out so it is compatible with part 2
|
||||
p.rooms[0].occupants[2] = Amber
|
||||
p.rooms[0].occupants[3] = Amber
|
||||
p.rooms[1].occupants[2] = Bronze
|
||||
p.rooms[1].occupants[3] = Bronze
|
||||
p.rooms[2].occupants[2] = Copper
|
||||
p.rooms[2].occupants[3] = Copper
|
||||
p.rooms[3].occupants[2] = Desert
|
||||
p.rooms[3].occupants[3] = Desert
|
||||
|
||||
for i := range p.rooms {
|
||||
p.rooms[i].position = 2 + 2*i
|
||||
}
|
||||
|
||||
return p
|
||||
}
|
||||
|
||||
func NewPuzzle2(input *bufio.Scanner) Puzzle {
|
||||
input.Scan()
|
||||
input.Scan()
|
||||
|
||||
r := regexp.MustCompile("[A-Z]")
|
||||
|
||||
var p Puzzle
|
||||
for i := 0; i < 2; i++ {
|
||||
input.Scan()
|
||||
line := r.FindAllString(input.Text(), 4)
|
||||
|
||||
for j, a := range line {
|
||||
p.rooms[j].occupants[i*3] = GetAmphipod(a)
|
||||
}
|
||||
}
|
||||
|
||||
// Fill in the middle lines
|
||||
p.rooms[0].occupants[1] = Desert
|
||||
p.rooms[0].occupants[2] = Desert
|
||||
p.rooms[1].occupants[1] = Copper
|
||||
p.rooms[1].occupants[2] = Bronze
|
||||
p.rooms[2].occupants[1] = Bronze
|
||||
p.rooms[2].occupants[2] = Amber
|
||||
p.rooms[3].occupants[1] = Amber
|
||||
p.rooms[3].occupants[2] = Copper
|
||||
|
||||
for i := range p.rooms {
|
||||
p.rooms[i].position = 2 + 2*i
|
||||
}
|
||||
|
||||
return p
|
||||
}
|
||||
|
||||
// The code to solve this is a complete mess
|
||||
// BUT it works, and that is all that matters for right now
|
||||
// Should really write an improved version of this though...
|
||||
func main() {
|
||||
challenge := aoc.New(2021, 23)
|
||||
|
||||
challenge.Test(`#############
|
||||
#...........#
|
||||
###B#C#B#D###
|
||||
#A#D#C#A#
|
||||
#########
|
||||
`, []int{12521, 44169})
|
||||
|
||||
challenge.Solution(1, func (input *bufio.Scanner) int {
|
||||
puzzle := NewPuzzle1(input)
|
||||
|
||||
_, cost := puzzle.Solve(0)
|
||||
|
||||
return cost
|
||||
})
|
||||
|
||||
challenge.Solution(2, func (input *bufio.Scanner) int {
|
||||
puzzle := NewPuzzle2(input)
|
||||
|
||||
_, cost := puzzle.Solve(0)
|
||||
|
||||
return cost
|
||||
})
|
||||
}
|
Loading…
Reference in New Issue
Block a user