package main import ( "bufio" "fmt" "os" "time" ) const ( DIR_N = -1i DIR_NE = 1 - 1i DIR_E = 1 DIR_SE = 1 + 1i DIR_S = 1i DIR_SW = -1 + 1i DIR_W = -1 DIR_NW = -1 - 1i CLEAR_SCREEN = "\033[H\033[2J" ) var scan []byte var next []byte var prevScans [][]byte var width, height int func main() { stdinToByteSlice() part1() part2() } func part1() { printScan() for i := 0; i < 10; i++ { fmt.Print(CLEAR_SCREEN) scan = tickToNext() printScan() time.Sleep(time.Millisecond * 250) } var ttlOpen, ttlTrees, ttlLmbr int for i := range scan { switch getByte(getPosFromInt(i)) { case '.': ttlOpen++ case '|': ttlTrees++ case '#': ttlLmbr++ } } _ = ttlOpen fmt.Println("= Part 1 =") fmt.Println(ttlTrees * ttlLmbr) } func part2() { var isDupe bool var i int target := 1000000000 for i = 0; i < target; i++ { next := tickToNext() if isDupe, prevScans = checkDuplicateState(next, prevScans); isDupe { i++ scan = next break } prevScans = append(prevScans, next) scan = next } // We need to find the state after `target` increments scan = prevScans[(target-i)%len(prevScans)] var ttlOpen, ttlTrees, ttlLmbr int for i := range scan { switch getByte(getPosFromInt(i)) { case '.': ttlOpen++ case '|': ttlTrees++ case '#': ttlLmbr++ } } _ = ttlOpen fmt.Println("= Part 2 =") fmt.Println(ttlTrees * ttlLmbr) } // checkDuplicateState returns the slice of prev that has all remaining available states func checkDuplicateState(s []byte, prev [][]byte) (bool, [][]byte) { for i, v := range prev { if areasAreEqual(s, v) { return true, prev[i:] } } return false, prev } func areasAreEqual(a1, a2 []byte) bool { for i := range a1 { if a1[i] != a2[i] { return false } } return true } func tickToNext() []byte { var ret []byte for i := 0; i < len(scan); i++ { c := getPosFromInt(i) _, sTree, sLmbr := getSurroundingCounts(c) b := getByte(c) switch b { case '.': if sTree >= 3 { ret = append(ret, '|') } else { ret = append(ret, '.') } case '|': if sLmbr >= 3 { ret = append(ret, '#') } else { ret = append(ret, '|') } case '#': if sLmbr > 0 && sTree > 0 { ret = append(ret, '#') } else { ret = append(ret, '.') } } } return ret } // getSurroundingCounts takes a pos and returns: // Number of open areas // Number of trees // Number of lumberyards // Surrounding that spot func getSurroundingCounts(c complex64) (int, int, int) { var sOpen, sTree, sLmbr int for _, v := range []complex64{DIR_NW, DIR_N, DIR_NE, DIR_W, DIR_E, DIR_SW, DIR_S, DIR_SE} { switch getByte(c + v) { case '.': sOpen++ case '|': sTree++ case '#': sLmbr++ } } return sOpen, sTree, sLmbr } func printScan() { for i := 0; i < len(scan)/width; i++ { fmt.Println(string(scan[i*width : (i+1)*width])) } } // getByte pulls a byte from the given position in the scan func getByte(pos complex64) byte { //idx := int(real(pos)) + int(imag(pos))*width if int(real(pos)) < 0 || int(imag(pos)) < 0 || int(real(pos)) >= width || int(imag(pos)) >= height { return 0 } return scan[int(real(pos))+int(imag(pos))*width] } func getPosComplex(x, y int) complex64 { return complex(float32(x), float32(y)) } func getPosFromInt(i int) complex64 { return complex(float32(i%width), float32(i/width)) } func getIdxFromPos(pos complex64) int { return int(real(pos)) + int(imag(pos))*width } func getCoordString(p complex64) string { return fmt.Sprintf("(%d,%d [%d])", int(real(p)), int(imag(p)), getIdxFromPos(p)) } func stdinToByteSlice() { scanner := bufio.NewScanner(os.Stdin) for scanner.Scan() { s := scanner.Bytes() if width == 0 { width = len(s) } scan = append(scan, s...) height++ } }