459 lines
8.4 KiB
Go
459 lines
8.4 KiB
Go
|
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)
|
||
|
}
|
||
|
}
|