Day 20 solved. Sometimes it produces no answer... some configurations seem to fail to render the correct image. ¯\_(ツ)_/¯
This commit is contained in:
parent
c604a4074f
commit
849fc74a15
458
2020/day20.go
Normal file
458
2020/day20.go
Normal file
|
@ -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)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user