package twenty import ( "bufio" "fmt" "math" "os" "strings" ) type tile struct { id int something [][]bool edges []string matches map[int]bool } type message struct { tiles []tile grid [][]*tile image [][]bool } func (t *tile) edge(side string) string { border := "" switch side { case "top": for _, c := range t.something[0] { if c { border += "#" } else { border += "." } } case "bottom": for _, c := range t.something[9] { if c { border += "#" } else { border += "." } } case "left": for _, row := range t.something { if row[0] { border += "#" } else { border += "." } } case "right": for _, row := range t.something { if row[9] { border += "#" } else { border += "." } } } return border } func (t *tile) flip() { flipped := [][]bool{} flipped = make([][]bool, 10) for i := 0; i < 10; i++ { flipped[i] = make([]bool, 10) } for y := 0; y < 10; y++ { for x := 0; x < 10; x++ { flipped[y][x] = t.something[9-y][9-x] } } t.something = flipped } func (t *tile) flipY() { flipped := [][]bool{} flipped = make([][]bool, 10) for i := 0; i < 10; i++ { flipped[i] = make([]bool, 10) } for y := 0; y < 10; y++ { for x := 0; x < 10; x++ { flipped[y][x] = t.something[9-y][x] } } t.something = flipped } func (t *tile) rotate() { rotated := [][]bool{} rotated = make([][]bool, 10) for i := 0; i < 10; i++ { rotated[i] = make([]bool, 10) } for y := 0; y < 10; y++ { for x := 0; x < 10; x++ { rotated[x][9-y] = t.something[y][x] } } t.something = rotated } func (m *message) load(filename string) error { file, err := os.Open(filename) if err != nil { return err } defer file.Close() m.tiles = []tile{} scanner := bufio.NewScanner(file) var t *tile for scanner.Scan() { if strings.Contains(scanner.Text(), "Tile") { t = &tile{} count, err := fmt.Sscanf(scanner.Text(), "Tile %4d:", &t.id) if count != 1 || err != nil { return fmt.Errorf("Unable to parse %s", scanner.Text()) } continue } if scanner.Text() == "" { m.tiles = append(m.tiles, *t) t = nil continue } if t != nil { row := []bool{} for _, c := range scanner.Text() { switch string(c) { case ".": row = append(row, false) case "#": row = append(row, true) } } t.something = append(t.something, row) } } if t != nil { m.tiles = append(m.tiles, *t) } for i := 0; i < len(m.tiles); i++ { m.tiles[i].matches = map[int]bool{} m.tiles[i].edges = []string{} m.tiles[i].edges = append(m.tiles[i].edges, m.tiles[i].edge("top")) m.tiles[i].edges = append(m.tiles[i].edges, m.tiles[i].edge("bottom")) m.tiles[i].edges = append(m.tiles[i].edges, m.tiles[i].edge("left")) m.tiles[i].edges = append(m.tiles[i].edges, m.tiles[i].edge("right")) m.tiles[i].flip() m.tiles[i].edges = append(m.tiles[i].edges, m.tiles[i].edge("top")) m.tiles[i].edges = append(m.tiles[i].edges, m.tiles[i].edge("bottom")) m.tiles[i].edges = append(m.tiles[i].edges, m.tiles[i].edge("left")) m.tiles[i].edges = append(m.tiles[i].edges, m.tiles[i].edge("right")) m.tiles[i].flip() } gridSize := int(math.Sqrt(float64(len(m.tiles)))) m.grid = make([][]*tile, gridSize) for i := 0; i < gridSize; i++ { m.grid[i] = make([]*tile, gridSize) } m.image = make([][]bool, gridSize*8) for i := 0; i < gridSize*8; i++ { m.image[i] = make([]bool, gridSize*8) } return nil } func (m *message) findCorners() []tile { // Compute neighbours for i := 0; i < len(m.tiles); i++ { for j := 0; j < len(m.tiles); j++ { if i == j { continue } for _, iEdge := range m.tiles[i].edges { for _, jEdge := range m.tiles[j].edges { if iEdge == jEdge { m.tiles[i].matches[m.tiles[j].id] = true } } } } } corners := []tile{} // Find neighbours with only 2 matches for i := 0; i < len(m.tiles); i++ { if len(m.tiles[i].matches) == 2 { corners = append(corners, m.tiles[i]) } } return corners } func (m *message) getTile(id int) *tile { for _, t := range m.tiles { if t.id == id { return &t } } return nil } func (m *message) inGrid(id int) bool { for y := 0; y < len(m.grid); y++ { for x := 0; x < len(m.grid); x++ { if m.grid[y][x] != nil { if id == m.grid[y][x].id { return true } } } } return false } func (m *message) layout() bool { corners := m.findCorners() for i := range corners { m.grid[0][0] = &corners[i] if m.placeNeighbours(0, 0) { return true } m.grid[0][0] = &corners[i] m.grid[0][0].rotate() if m.placeNeighbours(0, 0) { return true } m.grid[0][0] = &corners[i] m.grid[0][0].rotate() if m.placeNeighbours(0, 0) { return true } m.grid[0][0] = &corners[i] m.grid[0][0].rotate() if m.placeNeighbours(0, 0) { return true } m.grid[0][0] = &corners[i] m.grid[0][0].rotate() m.grid[0][0].flip() if m.placeNeighbours(0, 0) { return true } m.grid[0][0] = &corners[i] m.grid[0][0].rotate() if m.placeNeighbours(0, 0) { return true } m.grid[0][0] = &corners[i] m.grid[0][0].rotate() if m.placeNeighbours(0, 0) { return true } m.grid[0][0] = &corners[i] m.grid[0][0].rotate() if m.placeNeighbours(0, 0) { return true } } return false } func (m *message) placeNeighbours(x, y int) bool { allGood := true if x+1 >= len(m.grid) && y+1 >= len(m.grid) { return true } possibleTiles := []tile{} for id := range m.grid[y][x].matches { if !m.inGrid(id) { possibleTiles = append(possibleTiles, *m.getTile(id)) } } if x+1 < len(m.grid) { if m.grid[y][x+1] == nil { right := m.grid[y][x].edge("right") found := false for _, p := range possibleTiles { for _, match := range p.edges { if match == right { found = true rfound := false for i := 0; i < 4; i++ { if right == p.edge("left") { found = true rfound = true m.grid[y][x+1] = &p allGood = m.placeNeighbours(x+1, y) break } p.rotate() } if rfound { break } p.flipY() for i := 0; i < 4; i++ { if right == p.edge("left") { found = true m.grid[y][x+1] = &p allGood = m.placeNeighbours(x+1, y) break } p.rotate() } } } if found { break } } allGood = found } else { allGood = m.grid[y][x].edge("right") == m.grid[y][x+1].edge("left") } } if y+1 < len(m.grid) { bottom := m.grid[y][x].edge("bottom") found := false for _, p := range possibleTiles { for _, match := range p.edges { if match == bottom { found = true rfound := false for i := 0; i < 4; i++ { if bottom == p.edge("top") { found = true rfound = true m.grid[y+1][x] = &p allGood = m.placeNeighbours(x, y+1) break } p.rotate() } if rfound { break } p.flipY() for i := 0; i < 4; i++ { if bottom == p.edge("top") { found = true m.grid[y+1][x] = &p allGood = m.placeNeighbours(x, y+1) break } p.rotate() } } } if found { break } allGood = false } } if !allGood { m.grid[y][x] = nil } return allGood } func (m *message) combineTiles() bool { for y := 0; y < len(m.grid); y++ { for x := 0; x < len(m.grid); x++ { if m.grid[y][x] == nil { return false } } } for y := 0; y < len(m.grid); y++ { for x := 0; x < len(m.grid); x++ { for tY := 1; tY < len(m.grid[y][x].something)-1; tY++ { for tX := 1; tX < len(m.grid[y][x].something[tY])-1; tX++ { imageY := (y * (len(m.grid[y][x].something) - 2)) + (tY - 1) imageX := (x * (len(m.grid[y][x].something[tY]) - 2)) + (tX - 1) m.image[imageY][imageX] = m.grid[y][x].something[tY][tX] } } } } return true } func (m *message) rotateImage() { rotated := [][]bool{} rotated = make([][]bool, len(m.image)) for i := 0; i < len(m.image); i++ { rotated[i] = make([]bool, len(m.image)) } for y := 0; y < 10; y++ { for x := 0; x < 10; x++ { rotated[x][9-y] = m.image[y][x] } } m.image = rotated } func (m *message) flipImage() { flipped := [][]bool{} flipped = make([][]bool, len(m.image)) for i := 0; i < len(m.image); i++ { flipped[i] = make([]bool, len(m.image)) } for y := 0; y < 10; y++ { for x := 0; x < 10; x++ { flipped[y][x] = m.image[9-y][x] } } m.image = flipped } func (m *message) printGrid() { output := "" for y := 0; y < len(m.grid); y++ { for tY := 0; tY < 10; tY++ { for x := 0; x < len(m.grid[y]); x++ { for tX := 0; tX < 10; tX++ { if m.grid[y][x].something[tY][tX] { output += "#" } else { output += "." } } } output += "\n" } } fmt.Printf(output) } func (m *message) printImage() { output := "" for _, row := range m.image { for _, val := range row { if val { output += "#" } else { output += "." } } output += "\n" } fmt.Printf(output) } func (m *message) alignMonsters() bool { for i := 0; i < 4; i++ { count := m.seaMonsters() if count > 0 { return true } m.rotateImage() } m.flipImage() for i := 0; i < 4; i++ { count := m.seaMonsters() if count > 0 { return true } m.rotateImage() } return false } func (m *message) cornerProduct() int { corners := m.findCorners() if len(corners) != 4 { return -1 } product := 1 for _, tile := range corners { product *= tile.id } return product } func (m *message) seaMonsters() int { monsters := 0 for y := 0; y < len(m.image)-2; y++ { for x := 0; x < len(m.image[0])-19; x++ { // Check if this is a valid seamonster if m.image[y][x+18] && m.image[y+1][x] && m.image[y+1][x+5] && m.image[y+1][x+6] && m.image[y+1][x+11] && m.image[y+1][x+12] && m.image[y+1][x+17] && m.image[y+1][x+18] && m.image[y+1][x+19] && m.image[y+2][x+1] && m.image[y+2][x+4] && m.image[y+2][x+7] && m.image[y+2][x+10] && m.image[y+2][x+13] && m.image[y+2][x+16] { monsters++ } } } return monsters } func (m *message) countRoughness() int { monsters := m.seaMonsters() * 15 total := 0 for y := 0; y < len(m.image); y++ { for x := 0; x < len(m.image[0]); x++ { if m.image[y][x] { total++ } } } return total - monsters } // PartOne Find the product of the ids of the four corners func PartOne() string { m := message{} if err := m.load("twenty/input.txt"); err != nil { return err.Error() } return fmt.Sprintf("The product of the four corners is %d", m.cornerProduct()) } // PartTwo How many # are not seamonsters. func PartTwo() string { m := message{} if err := m.load("twenty/input.txt"); err != nil { return err.Error() } m.layout() m.combineTiles() m.alignMonsters() return fmt.Sprintf("There are %d # that are not seamonsters", m.countRoughness()) }