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

389 lines
9.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/example2"
instructions := ReadInstructionas2(filename)
h, w := calcHeightWidth(instructions)
log.Printf("read %+v instructions", instructions)
field := CreateField(h, w)
fmt.Println(field.String())
borderAmount := field.digByInstructions(instructions)
// log.Println("created field", field.BorderCellCols)
fmt.Println(field.String())
// WriteToFile("borders.txt", field.String())
// convert -size 3000x6000 xc:white -font "FreeMono" -pointsize 13 -fill black -draw @borders.txt borders.png
log.Printf("starting dig inside for cols %d-%d and rows %d-%d ", field.MinCol, field.MaxCol, field.MinRow, field.MaxRow)
insideAmount := field.digInsides()
log.Printf("border is %d; inside is %d", borderAmount, insideAmount)
fmt.Println(field.String())
// fmt.Println(field.Height, field.Width)
// WriteToFile("fulldug.txt", field.String())
// convert -size 3000x6000 xc:white -font "FreeMono" -pointsize 13 -fill black -draw @fulldug.txt fulldug.png
// field.countDugOut()
return borderAmount + insideAmount
}
// 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 ReadInstructionas2(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, ReadInstruction2(line))
}
return
}
func ReadInstruction2(line string) Instruction {
fields := strings.Fields(line)
hexDist := fields[2][2 : len(fields[2])-2]
hexDirection := fields[2][len(fields[2])-2 : len(fields[2])-1]
var direction Direction
switch hexDirection {
case "0":
direction = Rightward
case "1":
direction = Downward
case "2":
direction = Leftward
case "3":
direction = Upward
}
dist, err := strconv.ParseUint(hexDist, 16, 64)
if err != nil {
panic(err)
}
return Instruction{
Steps: int(dist),
Direction: direction,
}
}
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 {
Col, Row int
}
func (c Coord) applyDirection(d Direction) Coord {
switch d {
case Upward:
c.Row -= 1
case Downward:
c.Row += 1
case Leftward:
c.Col -= 1
case Rightward:
c.Col += 1
}
return c
}
type Cell struct {
IsDug bool
ToBeDug bool
Coord Coord
}
type Field struct {
Height, Width int
// Cells [][]*Cell
Cells map[Coord]*Cell
MinRow, MaxRow, MinCol, MaxCol int
// TODO - make this map[int]map[int]any (for the set)
BorderCellCols map[int][]int // known row -> col
}
func (f *Field)confirmCoord(c Coord) {
// log.Printf("configming coord %+v", c)
f.BorderCellCols[c.Row] = append(f.BorderCellCols[c.Row], c.Col)
if c.Row - 3 < f.MinRow {
f.MinRow = c.Row - 3
}
if c.Row + 3 > f.MaxRow {
f.MaxRow = c.Row + 3
}
if c.Col - 3 < f.MinCol {
f.MinCol = c.Col - 3
}
if c.Col + 3 > f.MaxCol {
f.MaxCol = c.Col + 3
}
}
func CreateField(height, width int) Field {
return Field{
Height: height, Width: width,
Cells: make(map[Coord]*Cell),
BorderCellCols: make(map[int][]int),
}
}
func (f *Field) digByInstructions(instructions []Instruction) (borderAmount int) {
runnerCoord := Coord{Col: 0, Row: 0}
f.Cells[runnerCoord] = &Cell{
IsDug: true,
}
// f.confirmCoord(runnerCoord) // should be confirmed when the cycle is closed on last step
// borderAmount += 1
for _, instruction := range instructions {
fmt.Printf("starting instruction %+v", instruction)
for i := 0; i < instruction.Steps; i++ {
runnerCoord = runnerCoord.applyDirection(instruction.Direction)
f.Cells[runnerCoord] = &Cell{
IsDug: true,
}
f.confirmCoord(runnerCoord)
borderAmount += 1
}
}
return
}
func (f *Field) String() string {
s := "text 15,15 \""
for row := f.MinRow; row <= f.MaxRow; row++ {
rowChars := make([]rune, f.MaxCol - f.MinCol + 1)
for col := f.MinCol; col <= f.MaxCol; col++ {
cell := f.Cells[Coord{col, row}]
if cell != nil && cell.ToBeDug {
rowChars[col - f.MinCol] = '@'
continue
}
if f.isCellDug(row, col) {
rowChars[col - f.MinCol] = '#'
} else {
rowChars[col - f.MinCol] = '.'
}
}
s += string(rowChars)
s += "\n"
}
s += "\""
return s
}
func (f *Field) digInsides() (countInside int) {
for row := f.MinRow; row < f.MaxRow; row++ {
if row % 10000 == 0 {
log.Printf("processed rows %d out of %d", row, f.MaxRow)
}
thisRowBorderCols := f.BorderCellCols[row]
slices.Sort(thisRowBorderCols)
// log.Printf("cols for row %d are %+v", row, thisRowBorderCols)
if len(thisRowBorderCols) == 0 {
continue
}
isInside := true
prevCol := thisRowBorderCols[0]
for _, col := range thisRowBorderCols[1:] {
gap := (col - prevCol - 1)
if gap == 0 {
prevCol = col
continue
}
// log.Printf("found gap in row %d. is inside %t. between col %d and %d of length %d",
// row, isInside, col, prevCol, gap)
if isInside {
for coll := prevCol+1; coll < col; coll++ {
f.Cells[Coord{Col: coll, Row: row}] = &Cell{
ToBeDug: true,
}
}
countInside += gap
}
isInside = !isInside
prevCol = col
}
}
return
}
// func (f *Field) digInsides() (countInside int) {
// for row := f.MinRow; row < f.MaxRow; row++ {
// if row % 10000 == 0 {
// log.Printf("processed rows %d out of %d", row, f.MaxRow)
// }
// isInside := false
// seenUp, seenDown := false, false // for detecting L---7 walls
// for col := f.MinCol; col < f.MaxCol; col++ {
// // TODO next optimization - for each row, store indices of cols with border cells
// // so that count of inside would be done by many at a time
// rightCellIsDug := f.isCellDug(row, col+1)
// if f.isCellDug(row, col) {
// upCellIsDug := f.isCellDug(row-1, col)
// downCellIsDug := f.isCellDug(row+1, col)
// if !rightCellIsDug {
// if (upCellIsDug && seenDown) || (downCellIsDug && seenUp) {
// isInside = !isInside
// }
// seenUp, seenDown = false, false
// }
// } else {
// // not a dug out cell, maybe inside and needs to be dug out
// if isInside {
// // f.Cells[Coord{col, row}] = &Cell{
// // ToBeDug: true,
// // }
// countInside += 1
// // log.Printf("tick count inside for %d %d", row, col)
// // cellPtr.ToBeDug = true
// }
// if rightCellIsDug {
// seenUp = f.isCellDug(row-1, col+1)
// seenDown = f.isCellDug(row+1, col+1)
// }
// }
// }
// }
// return
// }
func (f *Field) isCellDug(row, col int) bool {
cell := f.Cells[Coord{col, row}]
return cell != nil && cell.IsDug
}
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)
}