package main
import (
"bufio"
"fmt"
"io"
"os"
"strings"
"github.com/perlw/advent_of_code/toolkit/grid"
)
// Tiles ...
type Tiles map[int]grid.Grid
// Possibilities ...
type Possibilities struct {
T map[int][]string
B map[int][]string
L map[int][]string
R map[int][]string
}
// Count ...
func (p Possibilities) Count() int {
return len(p.T) + len(p.B) + len(p.L) + len(p.R)
}
func addPossibility(
p Possibilities, l, r, t, b bool, transform string, id int,
) Possibilities {
if t && strings.Contains(transform, "h") {
if _, ok := p.B[id]; !ok {
p.B[id] = make([]string, 0, 10)
}
p.B[id] = append(p.B[id], transform)
} else if t && !strings.Contains(transform, "h") {
if _, ok := p.T[id]; !ok {
p.T[id] = make([]string, 0, 10)
}
p.T[id] = append(p.T[id], transform)
}
if b && strings.Contains(transform, "h") {
if _, ok := p.T[id]; !ok {
p.T[id] = make([]string, 0, 10)
}
p.T[id] = append(p.T[id], transform)
} else if b && !strings.Contains(transform, "h") {
if _, ok := p.B[id]; !ok {
p.B[id] = make([]string, 0, 10)
}
p.B[id] = append(p.B[id], transform)
}
if l && strings.Contains(transform, "v") {
if _, ok := p.R[id]; !ok {
p.R[id] = make([]string, 0, 10)
}
p.R[id] = append(p.R[id], transform)
} else if l && !strings.Contains(transform, "v") {
if _, ok := p.L[id]; !ok {
p.L[id] = make([]string, 0, 10)
}
p.L[id] = append(p.L[id], transform)
}
if r && strings.Contains(transform, "v") {
if _, ok := p.L[id]; !ok {
p.L[id] = make([]string, 0, 10)
}
p.L[id] = append(p.L[id], transform)
} else if r && !strings.Contains(transform, "v") {
if _, ok := p.R[id]; !ok {
p.R[id] = make([]string, 0, 10)
}
p.R[id] = append(p.R[id], transform)
}
return p
}
func (ts Tiles) buildPosssibilities(debug bool) map[int]Possibilities {
result := make(map[int]Possibilities)
for i := range ts {
result[i] = Possibilities{
T: make(map[int][]string),
B: make(map[int][]string),
L: make(map[int][]string),
R: make(map[int][]string),
}
var count int
for j := range ts {
if j == i {
continue
}
tile := ts[j]
g := *(&tile).Clone()
l, r, t, b := compareEdges(ts[i], g)
if l || r || t || b {
count++
result[i] = addPossibility(result[i], l, r, t, b, "none", j)
if debug {
fmt.Printf("potential %d -> %d\n", i, j)
}
}
g = *(&tile).Clone()
hflipGrid(&g)
l, r, t, b = compareEdges(ts[i], g)
if l || r || t || b {
count++
result[i] = addPossibility(result[i], l, r, t, b, "hflip", j)
if debug {
fmt.Printf("potential hflip %d -> %d\n", i, j)
}
}
g = *(&tile).Clone()
vflipGrid(&g)
l, r, t, b = compareEdges(ts[i], g)
if l || r || t || b {
count++
result[i] = addPossibility(result[i], l, r, t, b, "vflip", j)
if debug {
fmt.Printf("potential vflip %d -> %d\n", i, j)
}
}
g = *(&tile).Clone()
hflipGrid(&g)
vflipGrid(&g)
l, r, t, b = compareEdges(ts[i], g)
if l || r || t || b {
count++
result[i] = addPossibility(result[i], l, r, t, b, "hvflip", j)
if debug {
fmt.Printf("potential hvflip %d -> %d\n", i, j)
}
}
g = *(&tile).Clone()
rotateGrid(&g)
l, r, t, b = compareEdges(ts[i], g)
if l || r || t || b {
count++
result[i] = addPossibility(
result[i], l, r, t, b, "rot", j,
)
if debug {
fmt.Printf("potential rot %d -> %d\n", i, j)
}
}
vflipGrid(&g)
l, r, t, b = compareEdges(ts[i], g)
if l || r || t || b {
count++
result[i] = addPossibility(
result[i], l, r, t, b, "vfliprot", j,
)
if debug {
fmt.Printf("potential vfliprot %d -> %d\n", i, j)
}
}
hflipGrid(&g)
l, r, t, b = compareEdges(ts[i], g)
if l || r || t || b {
count++
result[i] = addPossibility(
result[i], l, r, t, b, "hfliprot", j,
)
if debug {
fmt.Printf("potential hfliprot %d -> %d\n", i, j)
}
}
vflipGrid(&g)
l, r, t, b = compareEdges(ts[i], g)
if l || r || t || b {
count++
result[i] = addPossibility(
result[i], l, r, t, b, "hvfliprot", j,
)
if debug {
fmt.Printf("potential hvfliprot %d -> %d\n", i, j)
}
}
}
if debug {
fmt.Printf("%d -> %d\n", i, count)
}
}
return result
}
func rotateGrid(g *grid.Grid) {
t := g.Clone()
for y := 0; y < g.Height; y++ {
for x := 0; x < g.Width; x++ {
a := (y * g.Width) + x
b := (x * g.Width) + (g.Width - y - 1)
t.Cells[b] = g.Cells[a]
}
}
*g = *t
}
func vflipGrid(g *grid.Grid) {
for y := 0; y < g.Height; y++ {
for x := 0; x < g.Width/2; x++ {
a := (y * g.Width) + x
b := (y * g.Width) + (g.Width - x - 1)
g.Cells[a], g.Cells[b] = g.Cells[b], g.Cells[a]
}
}
}
func hflipGrid(g *grid.Grid) {
for y := 0; y < g.Height/2; y++ {
for x := 0; x < g.Width; x++ {
a := (y * g.Width) + x
b := ((g.Height - y - 1) * g.Width) + x
g.Cells[a], g.Cells[b] = g.Cells[b], g.Cells[a]
}
}
}
func compareEdges(a, b grid.Grid) (bool, bool, bool, bool) {
lEdge, rEdge, tEdge, bEdge := true, true, true, true
opEdge := (b.Height - 1) * b.Width
for x := 0; x < a.Width; x++ {
if a.Cells[x] != b.Cells[opEdge+x] {
tEdge = false
break
}
}
for x := 0; x < a.Width; x++ {
if a.Cells[((a.Height-1)*a.Height)+x] != b.Cells[x] {
bEdge = false
break
}
}
opEdge = b.Width - 1
for y := 0; y < a.Height; y++ {
if a.Cells[y*a.Width] != b.Cells[(y*b.Width)+opEdge] {
lEdge = false
break
}
}
for y := 0; y < a.Height; y++ {
if a.Cells[(y*a.Width)+a.Width-1] != b.Cells[y*b.Width] {
rEdge = false
break
}
}
return lEdge, rEdge, tEdge, bEdge
}
func inSlice(s []int, a int) bool {
for i := range s {
if s[i] == a {
return true
}
}
return false
}
// Task1 ...
func Task1(input Tiles, debug bool) int {
possibilities := input.buildPosssibilities(debug)
if debug {
fmt.Printf("%+v\n", possibilities)
}
result := 1
for i := range possibilities {
if possibilities[i].Count() == 2 {
result *= i
}
}
return result
}
// Task2 ...
func Task2(input Tiles, debug bool) int {
possibilities := input.buildPosssibilities(debug)
if debug {
fmt.Printf("%+v\n", possibilities)
}
corners := struct {
tl, tr, bl, br int
}{}
fmt.Println()
for i, p := range possibilities {
if p.Count() == 2 {
if len(p.T) > 0 && len(p.R) > 0 {
fmt.Printf("tl: %d %+v\n", i, p)
corners.tl = i
}
if len(p.T) > 0 && len(p.L) > 0 {
fmt.Printf("tr: %d %+v\n", i, p)
corners.tr = i
}
if len(p.B) > 0 && len(p.R) > 0 {
fmt.Printf("bl: %d %+v\n", i, p)
corners.bl = i
}
if len(p.B) > 0 && len(p.L) > 0 {
fmt.Printf("br: %d %+v\n", i, p)
corners.br = i
}
}
}
fmt.Printf("%+v\n", corners)
// find corner to corner paths
/*for _, i := range corners {
fmt.Printf("searching from %d\n", i)
c := i
found:
for {
next:
for j := range possibilities[c] {
if c == j {
continue
}
fmt.Printf("checking %d\n", j)
for k := range possibilities[j] {
if c == k {
continue
}
if len(possibilities[k]) == 2 {
fmt.Printf("corner %d\n", k)
// TODO: store
break found
} else if len(possibilities[k]) == 3 {
fmt.Printf("found next %d\n", k)
c = k
break next
}
}
}
}
}*/
return -1
}
func readInput(reader io.Reader) Tiles {
input := make(Tiles)
scanner := bufio.NewScanner(reader)
var currentID int
cells := make([]rune, 0, 10)
var stride int
for scanner.Scan() {
line := scanner.Text()
if line == "" {
input[currentID] = grid.FromRunes(cells, stride, stride)
cells = make([]rune, 0, 10)
continue
}
if strings.HasPrefix(line, "Tile") {
fmt.Sscanf(line, "Tile %d", ¤tID)
} else {
if len(cells) == 0 {
stride = len(line)
}
cells = append(cells, []rune(line)...)
}
}
input[currentID] = grid.FromRunes(cells, stride, stride)
return input
}
func main() {
var input Tiles
file, _ := os.Open("input.txt")
input = readInput(file)
file.Close()
result := Task1(input, false)
fmt.Printf("Task 1: %d\n", result)
result = Task2(input, false)
fmt.Printf("Task 2: %d\n", result)
}