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) log.Println("created field") // fmt.Println(field.String()) borderAmount := field.digByInstructions(instructions) 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 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 { 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 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{} // } } 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) (borderAmount int) { runnerCoord := Coord{X: 0, Y: 0} row, col := f.coordToIndices(runnerCoord) f.Cells[row][col] = &Cell{ IsDug: true, } // borderAmount += 1 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] = &Cell{ IsDug: true, } borderAmount += 1 } } return } func (f *Field) String() string { s := "text 15,15 \"" firstNonemptyRow, lastNonemptyRow := 0, 0 log.Print("just use", firstNonemptyRow, lastNonemptyRow) seenInRows := false firstNonemptyCol, lastNonemptyCol := 0, 0 seenInCols := false for i, row := range f.Cells { seenInRow := false for j := 0; j < len(row); j++ { if f.isCellDug(i, j) { seenInRow = true } } 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++ { if f.isCellDug(row, col) { seenInCol = true } } seenInCols = seenInCols || seenInCol if seenInCol { lastNonemptyCol = col } if !seenInCols { firstNonemptyCol = col } } rowLen := lastNonemptyCol - firstNonemptyCol + 1 log.Print(rowLen) for i := 0; i <= f.Height-1; i++ { rowChars := make([]rune, f.Width) for col := 0; col <= f.Width-1; col++ { cell := f.Cells[i][col] if cell != nil && cell.ToBeDug { rowChars[col] = '@' } else if f.isCellDug(i, col) { rowChars[col] = '#' } else { rowChars[col] = '.' } } s += string(rowChars) s += "\n" } s += "\"" return s } func (f *Field) digInsides() (countInside int) { for row := 1; row < f.Height-1; row++ { isInside := false seenUp, seenDown := false, false // for detecting L---7 walls for col := 0; col < f.Width-1; col++ { 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[row][col] = &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[row][col] return cell != nil && cell.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) }