github.com/mnlphlp/aoc22@v0.0.0-20230330151331-c1dc4bff1b9b/day18/day18.go (about)

     1  package day18
     2  
     3  import (
     4  	"fmt"
     5  	"strings"
     6  
     7  	"github.com/mnlphlp/aoc22/util"
     8  )
     9  
    10  func parseInput(input string) map[util.Pos3]struct{} {
    11  	droplet := make(map[util.Pos3]struct{}, 0)
    12  	for _, line := range strings.Split(input, "\n") {
    13  		if line == "" {
    14  			continue
    15  		}
    16  		pos := util.Pos3{}
    17  		fmt.Sscanf(line, "%d,%d,%d", &pos.X, &pos.Y, &pos.Z)
    18  		droplet[pos] = struct{}{}
    19  	}
    20  	return droplet
    21  }
    22  
    23  // all 6 directions
    24  var directions = []util.Pos3{util.Pos3{1, 0, 0}, util.Pos3{-1, 0, 0}, util.Pos3{0, 1, 0}, util.Pos3{0, -1, 0}, util.Pos3{0, 0, 1}, util.Pos3{0, 0, -1}}
    25  
    26  func surfaceArea(droplet map[util.Pos3]struct{}) int {
    27  	surface := 0
    28  	for pos := range droplet {
    29  		// check how many faces are not covered by other cubes
    30  		for _, d := range directions {
    31  			if _, ok := droplet[pos.Add(d)]; !ok {
    32  				surface++
    33  			}
    34  		}
    35  	}
    36  	return surface
    37  }
    38  
    39  func pocketSurfaceArea(droplet map[util.Pos3]struct{}) int {
    40  	// find the smallest and largest x, y and z coordinates
    41  	min, max := util.Pos3{X: 0, Y: 0, Z: 0}, util.Pos3{X: 0, Y: 0, Z: 0}
    42  	for pos := range droplet {
    43  		min = util.MinPos(min, pos)
    44  		max = util.MaxPos(max, pos)
    45  	}
    46  	// find closed pockets in the droplet
    47  	pockets := make(map[util.Pos3]struct{}, 0)
    48  	for x := min.X + 1; x < max.X; x++ {
    49  		for y := min.Y + 1; y < max.Y; y++ {
    50  			for z := min.Z + 1; z < max.Z; z++ {
    51  				pos := util.Pos3{X: x, Y: y, Z: z}
    52  				if _, solid := droplet[pos]; solid {
    53  					continue
    54  				}
    55  				if _, inPocket := pockets[pos]; inPocket {
    56  					skipDistance := 0
    57  					for inPocket {
    58  						skipDistance++
    59  						_, inPocket = pockets[pos.Add(util.Pos3{X: 0, Y: 0, Z: skipDistance})]
    60  					}
    61  					z += skipDistance - 1
    62  					continue
    63  				}
    64  				// open spot found, might be a pocket
    65  				// check if the opening is connected to the outside
    66  				// If not add to pockets
    67  				pocketTmp := make(map[util.Pos3]struct{})
    68  				open := make(map[util.Pos3]struct{}, 0)
    69  				open[pos] = struct{}{}
    70  				for len(open) > 0 {
    71  					// get first element
    72  					var p util.Pos3
    73  					for p = range open {
    74  						delete(open, p)
    75  						pocketTmp[p] = struct{}{}
    76  						break
    77  					}
    78  					// check if it is connected to the outside
    79  					if p.X <= min.X || p.X >= max.X || p.Y <= min.Y || p.Y >= max.Y || p.Z <= min.Z || p.Z >= max.Z {
    80  						pocketTmp = nil
    81  						break
    82  					}
    83  					// add all neighbors to open
    84  					for _, d := range directions {
    85  						n := p.Add(d)
    86  						if _, solid := droplet[n]; !solid {
    87  							if _, inPocket := pocketTmp[n]; !inPocket {
    88  								open[n] = struct{}{}
    89  							}
    90  						}
    91  					}
    92  				}
    93  				for p := range pocketTmp {
    94  					pockets[p] = struct{}{}
    95  				}
    96  
    97  			}
    98  		}
    99  	}
   100  	// calculate surface area of pockets
   101  	return surfaceArea(pockets)
   102  }
   103  
   104  func Solve(input string, debug bool, task int) (string, string) {
   105  	res1, res2 := 0, 0
   106  	droplet := parseInput(input)
   107  	if debug {
   108  		fmt.Println("Droplet: ", droplet)
   109  	}
   110  	if task != 2 {
   111  		res1 = surfaceArea(droplet)
   112  		fmt.Println("Result 1: ", res1)
   113  	}
   114  	if task != 1 {
   115  		if res1 == 0 {
   116  			res1 = surfaceArea(droplet)
   117  		}
   118  		pocketArea := pocketSurfaceArea(droplet)
   119  		res2 = res1 - pocketArea
   120  		fmt.Println("Result 2: ", res2)
   121  	}
   122  
   123  	return fmt.Sprint(res1), fmt.Sprint(res2)
   124  }