adventofcode/2017/day21/day21.go

318 lines
6.7 KiB
Go

package main
import (
"bufio"
"fmt"
"log"
"math"
"os"
"strings"
"time"
)
var allRules []Rule
func main() {
inp := StdinToStrings()
for i := range inp {
allRules = append(allRules, Rule(inp[i]))
}
//pattern := Pattern(".#./..#/###")
pattern := Pattern("../##")
fmt.Println(pattern)
if !pattern.IsValid() {
fmt.Println("Invalid Pattern Given. Must be square.")
}
ClearScreen()
pattern.PrettyPrint()
time.Sleep(time.Second)
for iters := 0; iters < 5; iters++ {
ClearScreen()
pattern = Tick(pattern)
pattern.PrettyPrint()
time.Sleep(time.Second)
}
fmt.Println("On:", pattern.GetOnCount())
}
// Stitch takes a slice of patterns and turns them into one large (square) pattern
func Stitch(inp []Pattern) Pattern {
ppr := int(math.Sqrt(float64(len(inp))))
newSize := ppr * inp[0].Size()
var currRow int
rows := make(map[int]string)
for i := 0; i < len(inp); i++ {
currRow = (i / ppr) * 2
for j := 0; j < len(inp[0]); j++ {
if len(rows[currRow+j]) == newSize {
rows[currRow+j] += "/"
}
rows[currRow+j] += inp[i].GetRow(j)
}
}
var lines []string
for i := 0; i < newSize; i++ {
lines = append(lines, rows[i])
}
return Pattern(strings.Join(lines, "/"))
}
// Run the pattern, or all of it's subpatterns
// through the rules and return the new pattern
func Tick(inp Pattern) Pattern {
if inp.SubpatternCount() > 1 {
var subs []Pattern
// Tick each subpattern
for i := 0; i < inp.SubpatternCount(); i++ {
if s := inp.GetSubpattern(i); s.IsValid() {
subs = append(subs, Tick(s))
} else {
log.Fatal("Error ticking pattern")
}
}
return Stitch(subs)
}
var foundMatch bool
for i := range allRules {
if allRules[i].Matches(inp) {
inp = allRules[i].Output()
foundMatch = true
}
}
if !foundMatch {
fmt.Println("~ ERROR TICKING ~")
inp.PrettyPrint()
fmt.Println("~ ERROR TICKING ~")
os.Exit(1)
}
return inp
}
/**
* A pattern is a string with some special methods
*/
type Pattern string
// Print a pattern prettily. (in a square)
func (p Pattern) PrettyPrint() {
pts := strings.Split(string(p), "/")
for i := range pts {
fmt.Println(pts[i])
}
}
func (p Pattern) GetOnCount() int {
return strings.Count(string(p), "#")
}
// Returns if a pattern is valid.
// A pattern is valid if it has more than 0 rows
// and every row is the same length as the number
// of columns
func (p Pattern) IsValid() bool {
pts := strings.Split(string(p), "/")
if len(pts) == 0 {
return false
}
for i := range pts {
if len(pts[i]) != len(pts) {
return false
}
}
return true
}
// Returns the "size" of the pattern
// That is, how many rows/columns it has
func (p Pattern) Size() int {
return len(strings.Split(string(p), "/"))
}
// Flip returns a new pattern that has been flipped horizontally
func (p Pattern) Flip() Pattern {
// We can only flip the smallest patterns (2x2 or 3x3)
if p.SubpatternCount() != 1 {
return p
}
pts := strings.Split(string(p), "/")
for i := range pts {
pts[i] = RevString(pts[i])
}
return Pattern(strings.Join(pts, "/"))
}
// Rotate returns a new pattern that has been rotated deg degrees
// Only right-angles
func (p Pattern) Rotate(deg int) Pattern {
// We can only rotate the smallest patterns (2x2 or 3x3)
if p.SubpatternCount() != 1 {
return p
}
if deg < 0 {
deg += 360
} else if deg == 0 {
return p
}
pts := strings.Split(string(p), "/")
ret := make([]string, len(pts))
use := p
switch deg {
case 90:
if p.Size()%3 == 0 {
ret[0] = RevString(use.GetCol(2))
ret[1] = RevString(use.GetCol(1))
ret[2] = RevString(use.GetCol(0))
} else {
ret[0] = RevString(use.GetCol(1))
ret[1] = RevString(use.GetCol(0))
}
case 180:
if p.Size()%3 == 0 {
ret[0] = RevString(use.GetRow(2))
ret[1] = RevString(use.GetRow(1))
ret[2] = RevString(use.GetRow(0))
} else {
ret[0] = RevString(use.GetRow(1))
ret[1] = RevString(use.GetRow(0))
}
case 270:
if p.Size()%3 == 0 {
ret[0] = use.GetCol(2)
ret[1] = use.GetCol(1)
ret[2] = use.GetCol(0)
} else {
ret[0] = use.GetCol(1)
ret[1] = use.GetCol(0)
}
}
use = Pattern(strings.Join(ret, "/"))
return Pattern(strings.Join(ret, "/"))
}
// GetRow returns a row as a string
func (p Pattern) GetRow(row int) string {
pts := strings.Split(string(p), "/")
if row >= len(pts) {
return ""
}
return pts[row]
}
// GetCol returns a column as a string
func (p Pattern) GetCol(col int) string {
var ret string
pts := strings.Split(string(p), "/")
if col >= len(pts[0]) {
return ""
}
for i := 0; i < len(pts); i++ {
ret = ret + string(pts[i][col])
}
return ret
}
// Counts the number of subpatterns in the pattern
func (p Pattern) SubpatternCount() int {
if p.Size()%2 == 0 {
return (p.Size() / 2) * (p.Size() / 2)
} else {
return (p.Size() / 3) * (p.Size() / 3)
}
}
// Gets a specific subpattern out of the pattern
func (p Pattern) GetSubpattern(i int) Pattern {
if i > p.SubpatternCount() {
return Pattern("")
}
subSize := 3 // Assume 3x3 subpatterns
if p.Size()%2 == 0 {
// Subpatterns are actually 2x2
subSize = 2
}
ppr := p.Size() / subSize
col := i % (p.Size() / subSize)
row := i / ppr
ptString := ""
for j := 0; j < subSize; j++ {
ptString += p.GetRow((row * 2) + j)[(col*subSize):(col*subSize)+subSize] + "/"
}
return Pattern(ptString[:len(ptString)-1])
}
/**
* Rule is an interface that can take a pattern and return if it matches,
* report on it's size, or return the resulting pattern from applying itself
* to a pattern
*/
type RuleFace interface {
Matches(inp Pattern) bool
InputSize() int
Apply(inp Pattern) Pattern
}
/**
* Rule implements the RuleFace interface
* for 2x2 => 3x3 transitions
* or 3x3 => 4x4 transitions
*/
type Rule string
func (r Rule) Input() Pattern {
pts := strings.Split(string(r), " ")
return Pattern(pts[0])
}
func (r Rule) Output() Pattern {
pts := strings.Split(string(r), " ")
return Pattern(pts[2])
}
func (r Rule) Matches(inp Pattern) bool {
if inp.Size() != r.InputSize() {
return false
}
// Try it rotated 90,180,270
for i := 0; i < 360; i += 90 {
if r.Equals(inp.Rotate(i)) || r.Equals(inp.Flip().Rotate(i)) {
return true
}
}
return false
}
func (r Rule) InputSize() int {
return len(strings.Split(string(r.Input()), "/"))
}
// Equals is a direct string comparison
func (r Rule) Equals(inp Pattern) bool {
return string(r.Input()) == string(inp)
}
func RevString(s string) string {
runes := []rune(s)
for i, j := 0, len(runes)-1; i < j; i, j = i+1, j-1 {
runes[i], runes[j] = runes[j], runes[i]
}
return string(runes)
}
func ClearScreen() {
fmt.Println("\033[H\033[2J")
}
func StdinToStrings() []string {
var input []string
scanner := bufio.NewScanner(os.Stdin)
for scanner.Scan() {
input = append(input, scanner.Text())
}
return input
}