package day18 import ( "fmt" "log" "os" "slices" "strconv" "strings" "sync" ) func Run() int { log.Println("hello day 18") log.Println("problem of lagoon bgins") filename := "day18/input" 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.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() (result int) { lineSum := make(chan int) var wg sync.WaitGroup rowsCount := f.MaxRow - f.MinRow wg.Add(rowsCount) done := make(chan bool) go func() { wg.Wait() close(lineSum) }() go func() { for rowInternalCount := range lineSum { result += rowInternalCount } close(done) }() for row := f.MinRow; row < f.MaxRow; row++ { go func(row int){ if row%10000 == 0 { log.Printf("processed rows %d out of %d", row, f.MaxRow) } specialBorders := f.BordersFromLeft[row] if len(specialBorders) == 0 { wg.Done() return } 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, // } } lineSum <- diff // countInside += diff } if specialBorder.border == Vertical { bordersCrossed += 1 } prevBorder = specialBorder } wg.Done() }(row) } <-done return result } // 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) }