diff --git a/2020/day20.go b/2020/day20.go new file mode 100644 index 0000000..0c7951d --- /dev/null +++ b/2020/day20.go @@ -0,0 +1,458 @@ +package main + +import ( + "fmt" + "log" + "os" + "regexp" + + "git.annabunch.es/annabunches/adventofcode/2020/lib/util" +) + +const ( + MATCH_NONE = iota + MATCH_TOP + MATCH_BOTTOM + MATCH_LEFT + MATCH_RIGHT +) + +type Tile struct { + id int + data []string + rotation int + flippedX bool + flippedY bool +} + +func NewTile() *Tile { + return &Tile{ + data: make([]string, 0), + rotation: 0, + flippedX: false, + flippedY: false, + } +} + +func (t *Tile) print() { + for _, line := range t.data { + fmt.Println(line) + } + fmt.Println() +} + +func (t *Tile) rotate() { + newData := make([]string, len(t.data)) + for i := len(t.data) - 1; i >= 0; i-- { + for j, char := range t.data[i] { + newData[j] += string(char) + } + } + t.data = newData + + t.rotation++ + if t.rotation > 3 { + t.rotation = 0 + } +} + +func (t *Tile) flipX() { + newData := make([]string, len(t.data)) + + for i := 0; i < len(newData); i++ { + newData[i] = t.data[len(t.data)-1-i] + } + + t.data = newData + t.flippedX = !t.flippedX +} + +func (t *Tile) flipY() { + newData := make([]string, len(t.data)) + + for i := 0; i < len(newData); i++ { + for j := 0; j < len(t.data[i]); j++ { + newData[i] += string(t.data[i][len(t.data)-1-j]) + } + } + + t.data = newData + t.flippedY = !t.flippedY +} + +func (t *Tile) reset() { + for t.rotation != 0 { + t.rotate() + } + + if t.flippedX { + t.flipX() + } + if t.flippedY { + t.flipY() + } +} + +func parseInput(input []string) map[int]*Tile { + tileMap := make(map[int]*Tile) + + re := regexp.MustCompile("^Tile ([0-9]+):$") + id := 0 + tile := NewTile() + for _, line := range input { + if re.MatchString(line) { + id = util.MustAtoi(re.FindStringSubmatch(line)[1]) + tile.id = id + continue + } + if line == "" { + tileMap[id] = tile + tile = NewTile() + id = 0 + continue + } + tile.data = append(tile.data, line) + } + + // if needed, add the last one (might be missing our final blank line) + if id != 0 { + tileMap[id] = tile + } + + return tileMap +} + +func matchTiles(oldTile, newTile *Tile) int { + for i := 0; i < 4; i++ { + check := subMatchTiles(oldTile, newTile) + if check != MATCH_NONE { + return check + } + newTile.rotate() + } + + newTile.reset() + newTile.flipX() + for i := 0; i < 4; i++ { + check := subMatchTiles(oldTile, newTile) + if check != MATCH_NONE { + return check + } + newTile.rotate() + } + + newTile.reset() + newTile.flipY() + for i := 0; i < 4; i++ { + check := subMatchTiles(oldTile, newTile) + if check != MATCH_NONE { + return check + } + newTile.rotate() + } + + newTile.reset() + newTile.flipX() + newTile.flipY() + for i := 0; i < 4; i++ { + check := subMatchTiles(oldTile, newTile) + if check != MATCH_NONE { + return check + } + newTile.rotate() + } + + return MATCH_NONE +} + +func subMatchTiles(oldTile, newTile *Tile) int { + // check top + if oldTile.data[0] == newTile.data[0] { + return MATCH_TOP + } + // check bottom + if oldTile.data[len(oldTile.data)-1] == newTile.data[len(newTile.data)-1] { + return MATCH_BOTTOM + } + // check left + match := true + for i := 0; i < len(oldTile.data); i++ { + if oldTile.data[i][0] != newTile.data[i][0] { + match = false + break + } + } + if match { + return MATCH_LEFT + } + + // check right + match = true + for i := 0; i < len(oldTile.data); i++ { + if oldTile.data[i][len(oldTile.data)-1] != newTile.data[i][len(newTile.data)-1] { + match = false + break + } + } + if match { + return MATCH_RIGHT + } + + return MATCH_NONE +} + +func arrangeTiles(tiles map[int]*Tile) map[[2]int]*Tile { + grid := make(map[[2]int]*Tile) + looseTiles := make([]*Tile, 0) + for _, v := range tiles { + looseTiles = append(looseTiles, v) + } + + // arbitrarily place a first tile + grid[[2]int{0, 0}] = looseTiles[0] + looseTiles = looseTiles[1:] + + for len(looseTiles) > 0 { + for coord, tile := range grid { + if _, ok := grid[[2]int{coord[0] + 1, coord[1]}]; ok { + if _, ok := grid[[2]int{coord[0] - 1, coord[1]}]; ok { + if _, ok := grid[[2]int{coord[0], coord[1] + 1}]; ok { + if _, ok := grid[[2]int{coord[0], coord[1] - 1}]; ok { + continue + } + } + } + } + + for i, loose := range looseTiles { + matched := matchTiles(tile, loose) + // On a match, flip appropriately and set coords + // check for already present tiles as well - skip if that's the case + var newCoords [2]int + willFlipX := true + switch matched { + case MATCH_TOP: + newCoords = [2]int{coord[0], coord[1] + 1} + case MATCH_BOTTOM: + newCoords = [2]int{coord[0], coord[1] - 1} + case MATCH_LEFT: + newCoords = [2]int{coord[0] - 1, coord[1]} + willFlipX = false + case MATCH_RIGHT: + newCoords = [2]int{coord[0] + 1, coord[1]} + willFlipX = false + } + + if matched != MATCH_NONE { + if _, ok := grid[newCoords]; ok { + continue + } + + if willFlipX { + loose.flipX() + } else { + loose.flipY() + } + + grid[newCoords] = loose + + if i == len(looseTiles)-1 { + looseTiles = looseTiles[:i] + } else { + looseTiles = append(looseTiles[:i], looseTiles[i+1:]...) + } + break // to avoid sequencing issues + } + } + } + } + + return grid +} + +func findCorners(grid map[[2]int]*Tile) []*Tile { + corners := make([]*Tile, 0) + + // min and max coord values + minX, minY, maxX, maxY := findLimits(grid) + + corners = append(corners, grid[[2]int{minX, minY}]) + corners = append(corners, grid[[2]int{maxX, minY}]) + corners = append(corners, grid[[2]int{minX, maxY}]) + corners = append(corners, grid[[2]int{maxX, maxY}]) + return corners +} + +func findLimits(grid map[[2]int]*Tile) (int, int, int, int) { + var minX, minY, maxX, maxY int + for coord, _ := range grid { + if coord[0] > maxX { + maxX = coord[0] + } + if coord[0] < minX { + minX = coord[0] + } + if coord[1] > maxY { + maxY = coord[1] + } + if coord[1] < minY { + minY = coord[1] + } + } + return minX, minY, maxX, maxY +} + +func stripBorder(tile *Tile) []string { + ret := make([]string, 0) + for i := 1; i < len(tile.data)-1; i++ { + ret = append(ret, tile.data[i][1:len(tile.data)-1]) + } + return ret +} + +func combineTiles(grid map[[2]int]*Tile) *Tile { + bigTile := NewTile() + minX, minY, maxX, maxY := findLimits(grid) + + for j := maxY; j >= minY; j-- { + row := make([]string, 8) + for i := minX; i <= maxX; i++ { + var tile *Tile + tile, ok := grid[[2]int{i, j}] + if !ok { + log.Panicf("Couldn't find tile: %d, %d", i, j) + } + + data := stripBorder(tile) + for i, line := range data { + row[i] = row[i] + line + } + } + bigTile.data = append(bigTile.data, row...) + } + + return bigTile +} + +var monsterPattern = []string{ + " # ", + "# ## ## ###", + " # # # # # # ", +} + +func subFilterMonsters(image *Tile) int { + numMonsters := 0 + for i := 0; i < len(image.data)-2; i++ { + for j := 0; j < len(image.data[0])-19; j++ { + if monsterCheck(image, i, j) { + removeMonster(image, i, j) + numMonsters++ + } + } + } + return numMonsters +} + +func removeMonster(image *Tile, i, j int) { + for y := 0; y < 3; y++ { + for x := 0; x < 20; x++ { + if monsterPattern[y][x] == '#' { + if j+x == len(image.data[i+y])-1 { + image.data[i+y] = image.data[i+y][:j+x] + "O" + } else { + image.data[i+y] = image.data[i+y][:j+x] + "O" + image.data[i+y][j+x+1:] + } + } + } + } +} + +func monsterCheck(image *Tile, i, j int) bool { + data := image.data + + for y := 0; y < 3; y++ { + for x := 0; x < 20; x++ { + if data[i+y][j+x] == '.' && monsterPattern[y][x] == '#' { + return false + } + } + } + + return true +} + +func filterMonsters(image *Tile) int { + numMonsters := 0 + + for i := 0; i < 4; i++ { + numMonsters = subFilterMonsters(image) + if numMonsters > 0 { + return numMonsters + } + image.rotate() + } + + image.reset() + image.flipX() + for i := 0; i < 4; i++ { + numMonsters = subFilterMonsters(image) + if numMonsters > 0 { + return numMonsters + } + } + + image.reset() + image.flipY() + for i := 0; i < 4; i++ { + numMonsters = subFilterMonsters(image) + if numMonsters > 0 { + return numMonsters + } + + } + + image.reset() + image.flipX() + image.flipY() + for i := 0; i < 4; i++ { + numMonsters = subFilterMonsters(image) + if numMonsters > 0 { + return numMonsters + } + } + + return numMonsters +} + +func main() { + step := os.Args[1] + values := util.InputParserStrings(os.Args[2]) + + tileMap := parseInput(values) + grid := arrangeTiles(tileMap) + + switch step { + case "1": + corners := findCorners(grid) + product := 1 + for _, tile := range corners { + product *= tile.id + } + fmt.Println(product) + case "2": + image := combineTiles(grid) + monsters := filterMonsters(image) + if monsters == 0 { + log.Panicf("Found no monsters") + } + count := 0 + for _, line := range image.data { + for _, char := range line { + if char == '#' { + count++ + } + } + } + fmt.Println(count) + } +}