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 } } } } 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 := 8; 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(tower.floors[z][x][y].ID) } 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 }