Files
advent-of-code-go/2018/day17/main.go
T

293 lines
6.5 KiB
Go

package main
import (
"flag"
"fmt"
"math"
"strings"
"github.com/alexchao26/advent-of-code-go/util"
)
func main() {
var part int
flag.IntVar(&part, "part", 1, "part 1 or 2")
flag.Parse()
fmt.Println("Running part", part)
if part == 1 {
ans := part1(util.ReadFile("./input.txt"))
util.CopyToClipboard(fmt.Sprintf("%v", ans))
fmt.Println("Output:", ans)
} else {
ans := part2(util.ReadFile("./input.txt"))
util.CopyToClipboard(fmt.Sprintf("%v", ans))
fmt.Println("Output:", ans)
}
}
func part1(input string) int {
grid, rowBounds := parseInputs(input)
pour(grid)
// // Uncomment to print part of the output
// end := len(grid[0])
// if end > 550 {
// end = 550
// }
// for i, v := range grid {
// fmt.Println(v[450:end])
// if i >= 60 {
// break
// }
// }
var ans int
for r, row := range grid {
for _, v := range row {
// NOTE: used x's instead of ~'s because they're easier to see
if (v == "|" || v == "x") && r <= rowBounds[1] && r >= rowBounds[0] {
ans++
}
}
}
return ans
}
func part2(input string) int {
grid, rowBounds := parseInputs(input)
pour(grid)
var ans int
for r, row := range grid {
for _, v := range row {
if v == "x" && r <= rowBounds[1] && r >= rowBounds[0] {
ans++
}
}
}
return ans
}
// x, y -> col, row...
func parseInputs(input string) (grid [][]string, rowBounds [2]int) {
lines := strings.Split(input, "\n")
verts, horis := [][3]int{}, [][3]int{}
var largestX, largestY int
lowestY := math.MaxInt32
for _, l := range lines {
var char1, char2 string
var num1, start, end int
fmt.Sscanf(l, "%1s=%d, %1s=%d..%d", &char1, &num1, &char2, &start, &end)
if char1 == "x" { // vert
verts = append(verts, [3]int{num1, start, end})
if num1 > largestX {
largestX = num1
}
if end > largestY {
largestY = end
}
if start < lowestY {
lowestY = start
}
} else {
horis = append(horis, [3]int{num1, start, end})
if num1 > largestY {
largestY = num1
}
if num1 < lowestY {
lowestY = num1
}
if end > largestX {
largestX = end
}
}
}
grid = make([][]string, largestY+1)
for i := range grid {
grid[i] = make([]string, largestX+1)
}
for _, coords := range verts {
col, start, end := coords[0], coords[1], coords[2]
for i := start; i <= end; i++ {
grid[i][col] = "#"
}
}
for _, coords := range horis {
row, start, end := coords[0], coords[1], coords[2]
for i := start; i <= end; i++ {
grid[row][i] = "#"
}
}
for i, row := range grid {
for j := range row {
if grid[i][j] != "#" {
grid[i][j] = "."
}
}
}
// add an empty row ot the bottom
grid = append(grid, make([]string, len(grid[0])))
for c := range grid[0] {
grid[len(grid)-1][c] = "."
}
return grid, [2]int{lowestY, largestY}
}
func pour(grid [][]string) {
// stack stores the coordinates that have been poured into
// the stack is used ot backtrack to previous cells to see if they can
// pour into additional spaces
stack := [][2]int{{0, 500}}
for len(stack) > 0 {
// take coordinate at top of stack
top := stack[len(stack)-1]
currentVal := grid[top[0]][top[1]]
// if it's a wall, pop it and continue
if currentVal == "#" {
stack = stack[:len(stack)-1]
continue
}
down := getNextCoord(top, "down")
// ensure it's in bounds, if not pop and continue
if !isInBounds(grid, down) {
stack = stack[:len(stack)-1]
continue
}
// this will happen on the second visit to a coordinate (it has to be
// changed into a pipe first @ the bottom of this loop)
if currentVal == "|" {
// transform will check if the row below (down variable) is water (pipes)
// bound by walls (assume that there isn't a sneaky hole in the floor)
// if it is bound by walls, it will replace all pipes with x's to
// indicate still water
transformStillWater(grid, down)
// pop off stack
stack = stack[:len(stack)-1]
// continue with the rest of the loop to add coords on the stack
// this handles two cases in particular
// |
// | pouring over to the right side here
// # | | #
// # | v #
// #-----###..#
// #-----# #..#
// #-----###..#
// #----------#
// ############
//
// |
// |
// # | #
// # | #
// #..|.......#
// #..|.......#
// #..|.......# <- filling up this row
// #----------#
// ############
}
// if below is a wall, append left and right to the stack
valDown := getValAt(grid, top, "down")
// add left and right to stack if they're sand
if valDown == "#" || valDown == "x" {
if getValAt(grid, top, "left") == "." {
stack = append(stack, getNextCoord(top, "left"))
}
if getValAt(grid, top, "right") == "." {
stack = append(stack, getNextCoord(top, "right"))
}
}
// if down is sand, add it to stack
if valDown == "." {
stack = append(stack, down)
}
// make self a water pipe
grid[top[0]][top[1]] = "|"
}
}
// helper functions to make getting the next coordinate or its value or if its in bounds
func isInBounds(grid [][]string, coord [2]int) bool {
return coord[0] < len(grid) && coord[1] < len(grid[0])
}
func getNextCoord(coord [2]int, direction string) [2]int {
if !strings.Contains("downleftright", direction) {
panic("invalid direction passed to getNextCoord")
}
switch direction {
case "down":
return [2]int{coord[0] + 1, coord[1]}
case "left":
return [2]int{coord[0], coord[1] - 1}
case "right":
return [2]int{coord[0], coord[1] + 1}
}
return [2]int{} // should never be hit...
}
func getValAt(grid [][]string, coord [2]int, direction string) string {
nextCoord := getNextCoord(coord, direction)
return grid[nextCoord[0]][nextCoord[1]]
}
// check a particular row to see if it can be transformed into still water
// i.e. is it all water pipes bound by walls on either end
func transformStillWater(grid [][]string, coord [2]int) {
var left, right int
isWalled := true
for col := coord[1] - 1; isInBounds(grid, [2]int{coord[0], col}); col-- {
if grid[coord[0]][col] == "#" {
left = col + 1
break
}
if grid[coord[0]][col] == "." {
isWalled = false
break
}
}
for col := coord[1] + 1; isInBounds(grid, [2]int{coord[0], col}); col++ {
if grid[coord[0]][col] == "#" {
right = col - 1
break
}
if grid[coord[0]][col] == "." {
isWalled = false
break
}
}
if isWalled {
for i := left; i <= right; i++ {
// only transform waters, not preexisting floors
if grid[coord[0]][i] == "|" {
grid[coord[0]][i] = "x"
}
}
}
}