mirror of
https://github.com/Threnklyn/advent-of-code-go.git
synced 2026-05-18 19:13:27 +02:00
293 lines
6.5 KiB
Go
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"
|
|
}
|
|
}
|
|
}
|
|
}
|