Advent-of-Code-2023/day18/lagoon.go

329 lines
7.1 KiB
Go

package day18
import (
"fmt"
"log"
"os"
"slices"
"strconv"
"strings"
)
func Run() int {
log.Println("hello day 18")
log.Println("problem of lagoon bgins")
filename := "day18/input"
instructions := ReadInstructionas(filename)
h, w := calcHeightWidth(instructions)
log.Printf("read %d instructions", len(instructions))
field := CreateField(h, w)
log.Println("created field")
// fmt.Println(field.String())
field.digByInstructions(instructions)
// WriteToFile("borders.txt", field.String())
// convert -size 3000x6000 xc:white -font "FreeMono" -pointsize 13 -fill black -draw @borders.txt borders.png
// fmt.Println(field.String())
// i'll start at (0,0), let's first just dig out the thing and check result
field.digInsides()
// WriteToFile("fulldug.txt", field.String())
// convert -size 3000x6000 xc:white -font "FreeMono" -pointsize 13 -fill black -draw @fulldug.txt fulldug.png
// fmt.Println(field.Height, field.Width)
return field.countDugOut()
}
// determine size of field. max(sum(up), sum(down)) for height,
// same for left and right,
// translate (0,0) into center of the field
//
// have cells, with coord. and i guess four sides, with color.
// i guess have directions, map[direction]color
// and have 'opposite' on directoin.
// for each direction apply it to cell coord, get cell, get opposite directoin and color it
//
// then have method on field and cell that excavates cell and colors all neighbors
//
// last part is filling in isides, should be ok with horizontal scans from left by even crossings
type Direction int
const (Upward Direction = iota
Downward
Leftward
Rightward)
func (d Direction)opposite() Direction {
switch d {
case Upward:
return Downward
case Downward:
return Upward
case Leftward:
return Rightward
case Rightward:
return Leftward
}
panic("unaccounted direction")
}
var DirectionNames []string = []string{"U", "D", "L", "R"}
func (d Direction)String() string {
return DirectionNames[d]
}
func DirectionFromString(s string) Direction {
index := slices.Index(DirectionNames, s)
if index == -1 {
panic(fmt.Sprint("bad direction", s))
}
return Direction(index)
}
type Instruction struct {
Direction Direction
Steps int
Color string
}
func ReadInstructionas(filename string) (result []Instruction) {
bytes, err := os.ReadFile(filename)
if err != nil {
panic(fmt.Sprint("error reading file: ", filename))
}
text := strings.TrimSpace(string(bytes))
for _, line := range strings.Split(text, "\n") {
result = append(result, ReadInstruction(line))
}
return
}
func ReadInstruction(line string) Instruction {
fields := strings.Fields(line)
direction := DirectionFromString(fields[0])
steps, err := strconv.Atoi(fields[1])
if err != nil {
panic(fmt.Sprint("bad steps in line: ", line))
}
color := fields[2][1 : len(fields[2])-1]
return Instruction{Direction: direction, Steps: steps, Color: color}
}
func calcHeightWidth(instructions []Instruction) (height, width int) {
movements := make(map[Direction]int)
for _, instr := range instructions {
movements[instr.Direction] += instr.Steps
}
if movements[Downward] > movements[Upward] {
height = 2 * movements[Downward]
} else {
height = 2 * movements[Upward]
}
if movements[Leftward] > movements[Rightward] {
width = 2 * movements[Leftward]
} else {
width = 2 * movements[Rightward]
}
height += 10
width += 10
return
}
type Coord struct {
X, Y int
}
func (c Coord)applyDirection(d Direction) Coord {
switch d {
case Upward:
c.Y -= 1
case Downward:
c.Y += 1
case Leftward:
c.X -= 1
case Rightward:
c.X += 1
}
return c
}
type Cell struct {
IsDug bool
ToBeDug bool
Walls map[Direction]string
Coord Coord
}
type Field struct {
Height, Width int
Cells [][]*Cell
}
func CreateField(height, width int) Field {
rows := make([][]*Cell, height)
for i := 0; i < height; i++ {
row := make([]*Cell, width)
rows[i] = row
for j := 0; j < width; j++ {
row[j] = &Cell{
Walls: make(map[Direction]string),
}
}
}
return Field{
Height: height, Width: width,
Cells: rows,
}
}
func (f *Field)coordToIndices(c Coord) (row, col int) {
row = c.Y + (f.Height/2)
col = c.X + (f.Width/2)
return
}
func (f *Field)digByInstructions(instructions []Instruction) {
runnerCoord := Coord{X: 0, Y: 0}
row, col := f.coordToIndices(runnerCoord)
f.Cells[row][col].IsDug = true
for _, instruction := range instructions {
log.Printf("starting instruction %+v", instruction)
for i := 0; i < instruction.Steps; i++ {
runnerCoord = runnerCoord.applyDirection(instruction.Direction)
row, col := f.coordToIndices(runnerCoord)
f.Cells[row][col].IsDug = true
}
}
return
}
func (f *Field)String() string {
s := "text 15,15 \""
firstNonemptyRow, lastNonemptyRow := 0,0
seenInRows := false
firstNonemptyCol, lastNonemptyCol := 0,0
seenInCols := false
for i, row := range f.Cells {
seenInRow := false
for _, cell := range row {
if cell.IsDug {
seenInRow = true
} else {
}
}
seenInRows = seenInRows || seenInRow
if seenInRow {
lastNonemptyRow = i
}
if !seenInRows {
firstNonemptyRow = i
}
}
for col := 0; col < f.Width; col++ {
seenInCol := false
for row := 0; row < f.Height; row++ {
cell := f.Cells[row][col]
if cell.IsDug {
seenInCol = true
}
}
seenInCols = seenInCols || seenInCol
if seenInCol {
lastNonemptyCol = col
}
if !seenInCols {
firstNonemptyCol = col
}
}
rowLen := lastNonemptyCol - firstNonemptyCol + 1
for i := firstNonemptyRow; i <= lastNonemptyRow; i++ {
rowChars := make([]rune, rowLen)
for col := firstNonemptyCol; col <= lastNonemptyCol; col++ {
cell := f.Cells[i][col]
if cell.IsDug {
rowChars[col-firstNonemptyCol] = '#'
} else if cell.ToBeDug {
rowChars[col-firstNonemptyCol] = '@'
} else {
rowChars[col-firstNonemptyCol] = '.'
}
}
s += string(rowChars)
s += "\n"
}
s += "\""
return s
}
func (f *Field)digInsides() {
for row := 0; row < f.Height; row++ {
isInside := false
seenUp, seenDown := false, false // for detecting L---7 walls
for col := 0; col < f.Width - 1; col++ {
cellPtr := f.Cells[row][col]
rightCell := f.Cells[row][col+1]
if cellPtr.IsDug {
upCell := f.Cells[row-1][col]
downCell := f.Cells[row+1][col]
if !rightCell.IsDug {
if (upCell.IsDug && seenDown) || (downCell.IsDug && seenUp) {
isInside = !isInside
}
seenUp, seenDown = false, false
}
} else {
// not a dug out cell, maybe inside and needs to be dug out
if isInside {
cellPtr.ToBeDug = true
}
if rightCell.IsDug {
nextUpCell := f.Cells[row-1][col+1]
nextDownCell := f.Cells[row+1][col+1]
seenUp = nextUpCell.IsDug
seenDown = nextDownCell.IsDug
}
}
}
}
}
func (f *Field)countDugOut() (result int) {
for _, row := range f.Cells {
for _, cell := range row {
if cell.IsDug || cell.ToBeDug {
result += 1
}
}
}
return
}
func WriteToFile(filename string, content string) {
fileBorder, err := os.Create(filename)
if err != nil {
panic(err)
}
defer func(){
if err := fileBorder.Close(); err != nil {
panic(err)
}
}()
fileBorder.WriteString(content)
}