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 }