package main import ( "fmt" "math" "strings" h "git.bullercodeworks.com/brian/adventofcode/helpers" ) func main() { inp := h.StdinToStringSlice() part1(inp) part2(inp) } func part1(inp []string) { scan := ParseSurface(inp) var surface int for k := range scan { for _, chk := range []pos{k.Top(), k.Bottom(), k.Left(), k.Right(), k.Front(), k.Back()} { if _, ok := scan[chk]; !ok { surface++ } } } fmt.Println("# Part 1") fmt.Println("Surface Area:", surface) } func part2(inp []string) { scan := ParseCubes(inp) scanExternal(scan) var surface int for _, cb := range scan { if cb.top == nil && !cb.topInt { surface++ } if cb.bottom == nil && !cb.bottomInt { surface++ } if cb.right == nil && !cb.rightInt { surface++ } if cb.left == nil && !cb.leftInt { surface++ } if cb.front == nil && !cb.frontInt { surface++ } if cb.back == nil && !cb.backInt { surface++ } } fmt.Println("# Part 2") fmt.Println("External Surface Area:", surface) } func scanExternal(m map[pos]*cube) { min := pos{x: math.MaxInt, y: math.MaxInt, z: math.MaxInt} max := pos{x: math.MinInt, y: math.MinInt, z: math.MinInt} for k := range m { min.x = h.Min(min.x, k.x) min.y = h.Min(min.y, k.y) min.z = h.Min(min.z, k.z) max.x = h.Max(max.x, k.x) max.y = h.Max(max.y, k.y) max.z = h.Max(max.z, k.z) } edges := make(map[pos]bool) for z := min.z - 1; z < max.z+1; z++ { for y := min.y - 1; y < max.y+1; y++ { for x := min.x - 1; x < max.x+1; x++ { edges[pos{min.x, y, z}] = true edges[pos{x, min.y, z}] = true edges[pos{x, y, min.z}] = true edges[pos{max.x, y, z}] = true edges[pos{x, max.y, z}] = true edges[pos{x, y, max.z}] = true } } } for k, v := range m { if v.left == nil && !canPath(k.Left(), edges, m) { v.leftInt = true } if v.right == nil && !canPath(k.Right(), edges, m) { v.rightInt = true } if v.top == nil && !canPath(k.Top(), edges, m) { v.topInt = true } if v.bottom == nil && !canPath(k.Bottom(), edges, m) { v.bottomInt = true } if v.front == nil && !canPath(k.Front(), edges, m) { v.frontInt = true } if v.back == nil && !canPath(k.Back(), edges, m) { v.backInt = true } } } func canPath(from pos, to map[pos]bool, scan map[pos]*cube) bool { queue := []pos{from} visit := make(map[pos]bool) result := false for len(queue) > 0 { cur := queue[0] queue = queue[1:] if _, ok := to[cur]; ok { result = true break } if _, ok := visit[cur]; ok { continue } visit[cur] = true moves := []pos{ cur.Top(), cur.Bottom(), cur.Left(), cur.Right(), cur.Front(), cur.Back(), } for _, mv := range moves { if _, ok := scan[mv]; ok { continue } queue = append(queue, mv) } } return result } func ParseSurface(inp []string) map[pos]bool { m := make(map[pos]bool) for i := range inp { pts := strings.Split(inp[i], ",") m[pos{ x: h.Atoi(pts[0]), y: h.Atoi(pts[1]), z: h.Atoi(pts[2]), }] = true } return m } func ParseCubes(inp []string) map[pos]*cube { m := make(map[pos]*cube) for i := range inp { pts := strings.Split(inp[i], ",") pos := pos{ x: h.Atoi(pts[0]), y: h.Atoi(pts[1]), z: h.Atoi(pts[2]), } m[pos] = &cube{position: pos} } for k, v := range m { if _, ok := m[k.Top()]; ok { m[k.Top()].bottom = v v.top = m[k.Top()] } if _, ok := m[k.Bottom()]; ok { m[k.Bottom()].top = v v.bottom = m[k.Bottom()] } if _, ok := m[k.Left()]; ok { m[k.Left()].right = v v.left = m[k.Left()] } if _, ok := m[k.Right()]; ok { m[k.Right()].left = v v.right = m[k.Right()] } if _, ok := m[k.Front()]; ok { m[k.Front()].back = v v.front = m[k.Front()] } if _, ok := m[k.Back()]; ok { m[k.Back()].front = v v.back = m[k.Back()] } } return m } type pos struct{ x, y, z int } func (p pos) Top() pos { return pos{x: p.x, y: p.y + 1, z: p.z} } func (p pos) Bottom() pos { return pos{x: p.x, y: p.y - 1, z: p.z} } func (p pos) Left() pos { return pos{x: p.x - 1, y: p.y, z: p.z} } func (p pos) Right() pos { return pos{x: p.x + 1, y: p.y, z: p.z} } func (p pos) Front() pos { return pos{x: p.x, y: p.y, z: p.z - 1} } func (p pos) Back() pos { return pos{x: p.x, y: p.y, z: p.z + 1} } type cube struct { position pos top, bottom *cube front, back *cube left, right *cube topInt, bottomInt bool frontInt, backInt bool leftInt, rightInt bool }