package main
import (
"bufio"
"fmt"
"os"
col "github.com/fatih/color"
)
func waitEnter() {
fmt.Printf("Press ENTER to continue\n")
bufio.NewReader(os.Stdin).ReadBytes('\n')
}
func copyExpandGrid(currW, currH, newW, newH int, grid []rune) []rune {
out := make([]rune, newW*newH)
for y := 0; y < currH; y++ {
for x := 0; x < currW; x++ {
out[(y*newW)+x] = grid[(y*currW)+x]
}
}
return out
}
var (
openColor = col.New(col.FgWhite)
forestColor = col.New(col.FgHiGreen)
lumberyardColor = col.New(col.FgHiBlue)
)
type Puzzle struct {
gridW, gridH int
gridSize int
grid []rune
numLines int
}
func NewPuzzle() *Puzzle {
input, err := os.Open("input.txt")
if err != nil {
panic(err.Error())
}
defer input.Close()
scanner := bufio.NewScanner(input)
p := Puzzle{}
for scanner.Scan() {
p.Add(scanner.Bytes())
}
if err := scanner.Err(); err != nil {
panic(err.Error())
}
return &p
}
func (p *Puzzle) Add(b []byte) {
oldW := p.gridW
oldH := p.gridH
oldSize := p.gridSize
if len(b) > p.gridW {
p.gridW = len(b)
p.gridH = len(b)
}
p.gridSize = p.gridW * p.gridH
if oldSize != p.gridSize {
p.grid = copyExpandGrid(oldW, oldH, p.gridW, p.gridH, p.grid)
}
i := p.numLines * p.gridW
for x, c := range b {
p.grid[i+x] = rune(c)
}
p.numLines++
}
func (p *Puzzle) PrintState() {
for y := 0; y < p.gridH; y++ {
for x := 0; x < p.gridW; x++ {
r := p.grid[(y*p.gridW)+x]
switch r {
case '.':
openColor.Printf("%c", r)
case '|':
forestColor.Printf("%c", '♠')
case '#':
lumberyardColor.Printf("%c", '⌂')
default:
fmt.Printf("%c", r)
}
}
fmt.Printf("\n")
}
}
func (p *Puzzle) collect(sx, sy int) (int, int) {
var trees, lumberyards int
for y := sy - 1; y < sy+2; y++ {
if y < 0 || y >= p.gridH {
continue
}
i := y * p.gridW
for x := sx - 1; x < sx+2; x++ {
if x < 0 || x >= p.gridW {
continue
}
if x == sx && y == sy {
continue
}
switch p.grid[i+x] {
case '|':
trees++
case '#':
lumberyards++
}
}
}
return trees, lumberyards
}
func (p *Puzzle) StepSim() {
tmp := make([]rune, p.gridSize)
for y := 0; y < p.gridH; y++ {
i := y * p.gridW
for x := 0; x < p.gridW; x++ {
trees, lumberyards := p.collect(x, y)
switch p.grid[i+x] {
case '.':
if trees >= 3 {
tmp[i+x] = '|'
continue
}
case '|':
if lumberyards >= 3 {
tmp[i+x] = '#'
continue
}
case '#':
if trees == 0 || lumberyards == 0 {
tmp[i+x] = '.'
continue
}
}
tmp[i+x] = p.grid[i+x]
}
}
p.grid = tmp
}
func (p *Puzzle) Score() int {
var trees, lumberyards int
for _, r := range p.grid {
if r == '#' {
lumberyards++
}
if r == '|' {
trees++
}
}
return trees * lumberyards
}
func main() {
// Solution 1
fmt.Println("Solution 1")
fmt.Println("==========")
p := NewPuzzle()
p.PrintState()
waitEnter()
for tick := 0; tick < 10; tick++ {
p.StepSim()
p.PrintState()
waitEnter()
}
fmt.Println("Score:", p.Score())
// Solution 2
fmt.Println("\nSolution 2")
fmt.Println("==========")
p = NewPuzzle()
p.PrintState()
fmt.Printf("\n")
seenGrids := make([][]rune, 0, 100000)
var from, to int
for tick := 0; tick < 1000000000; tick++ {
p.StepSim()
for prev, seen := range seenGrids {
equal := true
for t := range p.grid {
if seen[t] != p.grid[t] {
equal = false
}
}
if equal {
from = prev
to = tick
fmt.Printf("Found cycle between tick %d and %d, score: %d, offset: %d\n", prev, tick, p.Score())
p.PrintState()
waitEnter()
break
}
}
tmp := make([]rune, p.gridSize)
copy(tmp, p.grid)
seenGrids = append(seenGrids, tmp)
if from > 0 && to > 0 {
break
}
}
dist := to - from
offset := (1000000000 - from) % dist
p = NewPuzzle()
for t := 0; t < from+offset; t++ {
p.StepSim()
}
p.PrintState()
fmt.Println("Score after 1000000000 iterations:", p.Score())
}