Okay, I had to seek some advice on this one. The orbital period + least-common-multiple solution was not coming to me naturally.
157 lines
3.2 KiB
Go
157 lines
3.2 KiB
Go
package days
|
|
|
|
import (
|
|
"fmt"
|
|
"math"
|
|
"strconv"
|
|
"strings"
|
|
|
|
u "parnic.com/aoc2019/utilities"
|
|
)
|
|
|
|
type moonData struct {
|
|
pos u.Vec3[int]
|
|
vel u.Vec3[int]
|
|
}
|
|
|
|
func (m *moonData) applyGravity(other *moonData) {
|
|
applyGravityAxis := func(pos1, pos2, vel1, vel2 *int) {
|
|
if *pos1 < *pos2 {
|
|
*vel1++
|
|
*vel2--
|
|
} else if *pos1 > *pos2 {
|
|
*vel1--
|
|
*vel2++
|
|
}
|
|
}
|
|
|
|
applyGravityAxis(&m.pos.X, &other.pos.X, &m.vel.X, &other.vel.X)
|
|
applyGravityAxis(&m.pos.Y, &other.pos.Y, &m.vel.Y, &other.vel.Y)
|
|
applyGravityAxis(&m.pos.Z, &other.pos.Z, &m.vel.Z, &other.vel.Z)
|
|
}
|
|
|
|
func (m *moonData) applyVelocity() {
|
|
m.pos.Add(m.vel)
|
|
}
|
|
|
|
func (m moonData) getPotentialEnergy() int {
|
|
return int(math.Abs(float64(m.pos.X))) +
|
|
int(math.Abs(float64(m.pos.Y))) +
|
|
int(math.Abs(float64(m.pos.Z)))
|
|
}
|
|
|
|
func (m moonData) getKineticEnergy() int {
|
|
return int(math.Abs(float64(m.vel.X))) +
|
|
int(math.Abs(float64(m.vel.Y))) +
|
|
int(math.Abs(float64(m.vel.Z)))
|
|
}
|
|
|
|
func (m moonData) getTotalEnergy() int {
|
|
return m.getPotentialEnergy() * m.getKineticEnergy()
|
|
}
|
|
|
|
type Day12 struct {
|
|
moons []*moonData
|
|
}
|
|
|
|
func (d *Day12) Parse() {
|
|
lines := u.GetStringLines("12p")
|
|
d.moons = make([]*moonData, len(lines))
|
|
for i, line := range lines {
|
|
trimmed := line[1 : len(line)-1]
|
|
vals := strings.Split(trimmed, ", ")
|
|
x, _ := strconv.Atoi(vals[0][2:])
|
|
y, _ := strconv.Atoi(vals[1][2:])
|
|
z, _ := strconv.Atoi(vals[2][2:])
|
|
d.moons[i] = &moonData{
|
|
pos: u.Vec3[int]{X: x, Y: y, Z: z},
|
|
}
|
|
}
|
|
}
|
|
|
|
func (d Day12) Num() int {
|
|
return 12
|
|
}
|
|
|
|
func (d Day12) copyMoons() []*moonData {
|
|
moons := make([]*moonData, len(d.moons))
|
|
for i, moon := range d.moons {
|
|
moonCopy := *moon
|
|
moons[i] = &moonCopy
|
|
}
|
|
|
|
return moons
|
|
}
|
|
|
|
func getAllEnergy(moons ...*moonData) int {
|
|
energy := 0
|
|
for _, moon := range moons {
|
|
energy += moon.getTotalEnergy()
|
|
}
|
|
return energy
|
|
}
|
|
|
|
func (d *Day12) Part1() string {
|
|
moons := d.copyMoons()
|
|
|
|
numSteps := 1000
|
|
|
|
for i := 0; i < numSteps; i++ {
|
|
for i, moon1 := range moons {
|
|
for _, moon2 := range moons[i+1:] {
|
|
moon1.applyGravity(moon2)
|
|
}
|
|
|
|
moon1.applyVelocity()
|
|
}
|
|
}
|
|
|
|
return fmt.Sprintf("Total energy after %d steps: %s%d%s", numSteps, u.TextBold, getAllEnergy(moons...), u.TextReset)
|
|
}
|
|
|
|
func (d *Day12) Part2() string {
|
|
moons := d.copyMoons()
|
|
|
|
orig := make([]u.Vec3[int], len(moons))
|
|
for i, moon := range moons {
|
|
orig[i] = moon.pos
|
|
}
|
|
period := u.Vec3[int]{}
|
|
|
|
for loops := 0; period.X == 0 || period.Y == 0 || period.Z == 0; loops++ {
|
|
for i, moon1 := range moons {
|
|
for _, moon2 := range moons[i+1:] {
|
|
moon1.applyGravity(moon2)
|
|
}
|
|
moon1.applyVelocity()
|
|
}
|
|
|
|
foundX := true
|
|
foundY := true
|
|
foundZ := true
|
|
for i, moon := range moons {
|
|
if moon.pos.X != orig[i].X || moon.vel.X != 0 {
|
|
foundX = false
|
|
}
|
|
if moon.pos.Y != orig[i].Y || moon.vel.Y != 0 {
|
|
foundY = false
|
|
}
|
|
if moon.pos.Z != orig[i].Z || moon.vel.Z != 0 {
|
|
foundZ = false
|
|
}
|
|
}
|
|
if foundX && period.X == 0 {
|
|
period.X = loops + 1
|
|
}
|
|
if foundY && period.Y == 0 {
|
|
period.Y = loops + 1
|
|
}
|
|
if foundZ && period.Z == 0 {
|
|
period.Z = loops + 1
|
|
}
|
|
}
|
|
|
|
stepsRequired := u.LCM(period.X, period.Y, period.Z)
|
|
return fmt.Sprintf("Iterations to reach a previous state: %s%d%s", u.TextBold, stepsRequired, u.TextReset)
|
|
}
|