package day22 import ( "fmt" "log" "slices" "strings" "gitea.paas.celticinfo.fr/oabrivard/aoc2023/utils" ) type Coord struct { x int y int z int } type Brick struct { ID int start Coord end Coord supportedBy map[int]*Brick supports map[int]*Brick } type Tower struct { floors [][][]*Brick maxFloors int floorWidth int } // parseBricks returns an array of bricks sorted by height func parseBricks(lines []string) []*Brick { bricks := []*Brick{} ID := 1 for _, line := range lines { parts := strings.Split(line, "~") start := utils.ParseIntArray(parts[0], ",") end := utils.ParseIntArray(parts[1], ",") newBrick := Brick{ ID: ID, start: Coord{start[0], start[1], start[2]}, end: Coord{end[0], end[1], end[2]}, supportedBy: map[int]*Brick{}, supports: map[int]*Brick{}, } if newBrick.start.x > newBrick.end.x || newBrick.start.y > newBrick.end.y || newBrick.start.z > newBrick.end.z { log.Fatalf("Input error for brick %v", newBrick) } bricks = append(bricks, &newBrick) ID++ } slices.SortFunc(bricks, func(a, b *Brick) int { return a.start.z - b.start.z }) return bricks } // create a tower with an empty floor at level 0 func NewTower(height, width int) Tower { floors := make([][][]*Brick, height+1) for z := range floors { floors[z] = make([][]*Brick, width) for x := range floors[z] { floors[z][x] = make([]*Brick, width) } } return Tower{floors, height, width} } func (tower *Tower) maxHeight() int { return tower.maxFloors } func (tower *Tower) intersections(brick *Brick, currZ int) []*Brick { bricks := []*Brick{} for x := brick.start.x; x <= brick.end.x; x++ { for y := brick.start.y; y <= brick.end.y; y++ { if tower.floors[currZ][x][y] != nil { bricks = append(bricks, tower.floors[currZ][x][y]) } } } return bricks } func (tower *Tower) layout(brick *Brick, currZ int) { brickHeight := brick.end.z - brick.start.z + 1 for z := currZ; z < currZ+brickHeight; z++ { for x := brick.start.x; x <= brick.end.x; x++ { for y := brick.start.y; y <= brick.end.y; y++ { tower.floors[z][x][y] = brick } } } brick.start.z = currZ if brickHeight > 1 { brick.end.z = currZ + brickHeight - 1 } else { brick.end.z = currZ } } func (tower *Tower) addBrick(brick *Brick) { currZ := tower.maxHeight() for currZ > 0 { intersections := tower.intersections(brick, currZ-1) if len(intersections) > 0 { // Layout the current brick on the current floor tower.layout(brick, currZ) // add the current brick to the "supports" list of each supporting brick // and add the supporting bricks to the "supportedBy" list of the current brick for _, supportingBrick := range intersections { supportingBrick.supports[brick.ID] = brick brick.supportedBy[supportingBrick.ID] = supportingBrick } // brick is layed out, exit function return } currZ-- } // layout the brick on the first floor tower.layout(brick, 1) } func (tower *Tower) Print() { //for z := tower.maxHeight(); z >= 0; z-- { for z := 20; z > 0; z-- { for x := 0; x < tower.floorWidth; x++ { for y := 0; y < tower.floorWidth; y++ { if tower.floors[z][x][y] != nil { fmt.Print(string(byte('A') - 1 + byte(tower.floors[z][x][y].ID)%26)) } else { fmt.Print(".") } } fmt.Println(" ") } fmt.Println(" ") } fmt.Println(" ") } func Part1(lines []string) (int, []*Brick) { bricks := parseBricks(lines) tower := NewTower(500, 10) for _, brick := range bricks { fmt.Printf("Placing brick %d (%v)~(%v)\n", brick.ID, brick.start, brick.end) tower.addBrick(brick) } count := 0 for _, brick := range bricks { canDisintegrate := true for _, supportedBrick := range brick.supports { if len(supportedBrick.supportedBy) == 0 { log.Fatalln("Bug ", brick) } if len(supportedBrick.supportedBy) == 1 { canDisintegrate = false } } if canDisintegrate { fmt.Println(brick.ID, string('A'+byte(brick.ID-1))) count++ } } tower.Print() return count, bricks } /* func Part2(lines []string) { // place bricks like Part1 // and change z coord of each brick once it is layed out bricks := parseBricks(lines) tower := NewTower(500, 10) for _, brick := range bricks { fmt.Printf("Placing brick %d (%v)~(%v)\n", brick.ID, brick.start, brick.end) tower.addBrick(brick) } // sort brick by z coord desc (highest first) slices.SortFunc(bricks, func(a, b *Brick) int { return b.start.z - a.start.z }) // for each brick // if the brick above the current brick has just the current brick as support // then add 1 + the total fall of the brick above (coming from the map "total fallen") // Store the value in the map "total fallen" using the ID of the current brick // end for count := 0 for _, brick := range bricks { for _, aboveBrick := range brick.supports { if len(aboveBrick.supportedBy) == 0 { log.Fatalln("Bug ", brick) } if len(aboveBrick.supportedBy) == 1 { // will fall } } } } */ func buildDropList(brick *Brick, droppedList *map[int]bool) { // Do not drop if the bricks below have not all been removed for _, supportedBy := range brick.supportedBy { if !(*droppedList)[supportedBy.ID] { return } } (*droppedList)[brick.ID] = true // the supported brick for _, supportedBrick := range brick.supports { dropAbove := true for _, supportedBy := range supportedBrick.supportedBy { if !(*droppedList)[supportedBy.ID] { dropAbove = false } } if dropAbove { buildDropList(supportedBrick, droppedList) // the drops from the supported brick } } } func Part2(lines []string) int { bricks := parseBricks(lines) tower := NewTower(500, 10) for _, brick := range bricks { //fmt.Printf("Placing brick %d (%v)~(%v)\n", brick.ID, brick.start, brick.end) tower.addBrick(brick) } // for debugging slices.SortFunc(bricks, func(a, b *Brick) int { return a.ID - b.ID }) sum := 0 for _, brick := range bricks { singleSupport := false for _, supportedBrick := range brick.supports { if len(supportedBrick.supportedBy) == 0 { log.Fatalln("Bug ", brick) } if len(supportedBrick.supportedBy) == 1 { singleSupport = true } } /* if singleSupport { disintegrated := map[int]bool{brick.ID: true} checkList := []*Brick{brick} for len(checkList) > 0 { // take first from checklist check := checkList[0] checkList = checkList[1:] // for all bricks above, check ... for _, above := range check.supports { bricksRemoved := 0 // whether all bricks below it are disintegrated ... for _, below := range above.supportedBy { if disintegrated[below.ID] { bricksRemoved++ } } if len(above.supportedBy) == bricksRemoved { // then it would fall checkList = append(checkList, above) disintegrated[above.ID] = true } } } // do not include the brick we are currently checking fmt.Printf("Brick %d would drop %d other bricks\n", brick.ID, len(disintegrated)-1) sum += len(disintegrated) - 1 } } */ if singleSupport { dropList := map[int]bool{} dropList[brick.ID] = true if brick.ID == 14 { fmt.Println("debug") } for _, supportedBrick := range brick.supports { buildDropList(supportedBrick, &dropList) } fmt.Printf("Brick %d would drop %d other bricks\n", brick.ID, len(dropList)-1) sum += len(dropList) - 1 } } tower.Print() return sum }