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) }