507 lines
12 KiB
Go
507 lines
12 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/example"
|
|
instructions := ReadInstructionas(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.BordersFromLeft)
|
|
|
|
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 BorderSymbol rune
|
|
// '' always left to right
|
|
const (Vertical BorderSymbol = '|'
|
|
ToDown BorderSymbol = '7'
|
|
ToUp BorderSymbol = 'J'
|
|
FromUp BorderSymbol = 'F'
|
|
FromDown BorderSymbol = 'L'
|
|
)
|
|
|
|
type Field struct {
|
|
Height, Width int
|
|
// Cells [][]*Cell
|
|
Cells map[Coord]*Cell
|
|
MinRow, MaxRow, MinCol, MaxCol int
|
|
BordersFromLeft map[int]map[int]BorderSymbol
|
|
}
|
|
func (f *Field)confirmCoord(c Coord) {
|
|
// log.Printf("configming coord %+v", c)
|
|
|
|
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),
|
|
BordersFromLeft: make(map[int]map[int]BorderSymbol),
|
|
}
|
|
}
|
|
|
|
func PutSymbIntoMMMMap(mmmap map[int]map[int]BorderSymbol, row, col int, symb BorderSymbol) {
|
|
rowMap := mmmap[row]
|
|
if rowMap == nil {
|
|
rowMap = make(map[int]BorderSymbol)
|
|
mmmap[row] = rowMap
|
|
}
|
|
rowMap[col] = symb
|
|
}
|
|
|
|
func (f *Field) digByInstructions(instructions []Instruction) (borderAmount int) {
|
|
// for the last turn
|
|
instructions = append(instructions, instructions[0])
|
|
// but also don't overcount the border
|
|
borderAmount -= instructions[0].Steps
|
|
|
|
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
|
|
|
|
var prevInstruction Instruction
|
|
firstInstruction := true
|
|
for _, instruction := range instructions {
|
|
log.Printf("starting new instruction %+v", instruction)
|
|
if !firstInstruction {
|
|
turn := getTurnAsIfGoingFromLeft(prevInstruction.Direction, instruction.Direction)
|
|
for _, theTurn := range turn {
|
|
log.Printf(">> putting turn %s", string(turn))
|
|
PutSymbIntoMMMMap(f.BordersFromLeft, runnerCoord.Row, runnerCoord.Col, theTurn)
|
|
}
|
|
}
|
|
firstInstruction = false
|
|
log.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
|
|
log.Printf("inside %+v updated border amount to %d", instruction, borderAmount)
|
|
|
|
if instruction.Direction == Upward || instruction.Direction == Downward {
|
|
_, alreadyCountedTurn := f.BordersFromLeft[runnerCoord.Row][runnerCoord.Col]
|
|
if !alreadyCountedTurn {
|
|
PutSymbIntoMMMMap(f.BordersFromLeft, runnerCoord.Row, runnerCoord.Col, Vertical)
|
|
}
|
|
}
|
|
|
|
}
|
|
prevInstruction = instruction
|
|
}
|
|
return
|
|
}
|
|
|
|
func getTurnAsIfGoingFromLeft(directionFrom, directionTo Direction) []BorderSymbol {
|
|
log.Printf("getTurnAsIfGoingFromLeft from %s to %s", directionFrom.String(), directionTo.String())
|
|
|
|
var symbol BorderSymbol
|
|
if directionTo == Rightward && directionFrom == Upward {
|
|
symbol = FromUp
|
|
}
|
|
if directionTo == Rightward && directionFrom == Downward {
|
|
symbol = FromDown
|
|
}
|
|
if directionTo == Leftward && directionFrom == Upward {
|
|
symbol = ToDown
|
|
}
|
|
if directionTo == Leftward && directionFrom == Downward {
|
|
symbol = ToUp
|
|
}
|
|
|
|
if directionFrom == Rightward && directionTo == Upward {
|
|
symbol = ToUp
|
|
}
|
|
if directionFrom == Rightward && directionTo == Downward {
|
|
symbol = ToDown
|
|
}
|
|
if directionFrom == Leftward && directionTo == Upward {
|
|
symbol = FromDown
|
|
}
|
|
if directionFrom == Leftward && directionTo == Downward {
|
|
symbol = FromUp
|
|
}
|
|
|
|
|
|
// panic(fmt.Sprint("got strange from %s to %s", directionFrom.String(), directionTo.String()))
|
|
return []BorderSymbol{symbol}
|
|
}
|
|
|
|
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++ {
|
|
|
|
rowBords := f.BordersFromLeft[row]
|
|
if rowBords != nil {
|
|
bord, exists := rowBords[col]
|
|
if exists {
|
|
rowChars[col - f.MinCol] = rune(bord)
|
|
continue
|
|
}
|
|
}
|
|
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)
|
|
}
|
|
specialBorders := f.BordersFromLeft[row]
|
|
if len(specialBorders) == 0 {
|
|
continue
|
|
}
|
|
type BorderItem struct {
|
|
border BorderSymbol
|
|
col int
|
|
}
|
|
rowBorders := make([]BorderItem, 0)
|
|
for col, borderSymbol := range specialBorders {
|
|
rowBorders = append(rowBorders, BorderItem{borderSymbol, col})
|
|
}
|
|
slices.SortFunc(rowBorders, func(a BorderItem, b BorderItem) int {
|
|
return a.col - b.col
|
|
})
|
|
|
|
// log.Printf(">>>>>>> for row %d sorted %+v", row, rowBorders)
|
|
prevBorder := rowBorders[0]
|
|
bordersCrossed := 0
|
|
if prevBorder.border == Vertical {
|
|
bordersCrossed += 1
|
|
}
|
|
for _, specialBorder := range rowBorders[1:] {
|
|
diff := specialBorder.col - prevBorder.col - 1
|
|
|
|
if specialBorder.border == ToUp && prevBorder.border == FromUp {
|
|
bordersCrossed += 1
|
|
prevBorder = specialBorder
|
|
continue
|
|
}
|
|
if specialBorder.border == ToDown && prevBorder.border == FromDown {
|
|
bordersCrossed += 1
|
|
prevBorder = specialBorder
|
|
continue
|
|
}
|
|
if specialBorder.border == ToUp && prevBorder.border == FromDown {
|
|
prevBorder = specialBorder
|
|
continue
|
|
}
|
|
if specialBorder.border == ToDown && prevBorder.border == FromUp {
|
|
prevBorder = specialBorder
|
|
continue
|
|
}
|
|
|
|
if bordersCrossed % 2 == 1 { // is in
|
|
for col := prevBorder.col+1; col < specialBorder.col; col++ {
|
|
f.Cells[Coord{Col: col, Row: row}] = &Cell{
|
|
ToBeDug: true,
|
|
}
|
|
}
|
|
countInside += diff
|
|
}
|
|
|
|
if specialBorder.border == Vertical {
|
|
bordersCrossed += 1
|
|
}
|
|
|
|
prevBorder = specialBorder
|
|
}
|
|
}
|
|
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)
|
|
}
|