318 lines
6.7 KiB
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
|
|
}
|