package main import ( "fmt" "os" "time" h "git.bullercodeworks.com/brian/adventofcode/helpers" ) const ( dirU = iota dirR dirD dirL rocks = 5 debug = false ) var ( emptyRow = []byte{'|', ' ', ' ', ' ', ' ', ' ', ' ', ' ', '|'} bottomRow = []byte{'+', '-', '-', '-', '-', '-', '-', '-', '+'} ) func main() { inp := h.StdinToString() //fmt.Println("# Part 1") //simulate(inp, 2022) fmt.Println("# Part 2") simulate(inp, 1000000000000) } func stateAndSleep(m *h.GrowUpCoordByteMap) { if !debug { return } fmt.Print(h.CLEAR_SCREEN) //fmt.Println() fmt.Println(m) time.Sleep(time.Second / 10) } var cache map[string][]int func simulate(jets string, numRocks int) { cache = make(map[string][]int) m := h.NewGrowUpCoordByteMap() m.PutBytes([][]byte{ bottomRow, emptyRow, emptyRow, emptyRow, emptyRow, emptyRow, emptyRow, emptyRow, }, h.Coordinate{X: 0, Y: 0}) m.StringEmptyIsSpace = true jetIdx := 0 rockType := 0 var state string var height int var turboHeight int var cacheDisabled bool for rockNum := 1; rockNum <= numRocks; rockNum++ { fmt.Println(h.CLEAR_SCREEN) fmt.Println("Simulating:", rockNum, "/", numRocks) h.PrintProgress(rockNum, numRocks) fmt.Println("\nHeight:", height) if !cacheDisabled { state, height = GetState(rockType, jetIdx, m) if v, ok := cache[state]; !cacheDisabled && ok { // Ok, we've got a duplicate. Go full turbo. //addHeight := height mult := numRocks / rockNum newRockNum := rockNum * mult turboHeight = height * mult /* for rockNum+v[1] <= numRocks { rockNum = rockNum + v[1] height = height + addHeight fmt.Println(h.CLEAR_SCREEN) fmt.Println("Simulating:", rockNum, "/", numRocks) h.PrintProgress(rockNum, numRocks) fmt.Println("\nHeight:", height) } */ fmt.Println(h.CLEAR_SCREEN) fmt.Println("Simulating:", rockNum, "/", numRocks) h.PrintProgress(rockNum, numRocks) fmt.Println("\nHeight:", height) fmt.Println("State", state, "\nV:", v, "\nMult:", mult, "\nRockNum:", rockNum, "\nNewRockNum:", newRockNum, "\nTurboHeight:", turboHeight) cacheDisabled = true os.Exit(0) } else { cache[state] = []int{rockNum, height} } } else { fmt.Println("\nHeight:", turboHeight+GetHeight(m)) } AddFallingRock(rockType, m) stateAndSleep(m) doJet := true for { if !doJet && AtRest(m) { break } shiftDir := dirD if doJet { switch jets[jetIdx] { case '>': shiftDir = dirR case '<': shiftDir = dirL } jetIdx = (jetIdx + 1) % len(jets) } ShiftRock(shiftDir, m) stateAndSleep(m) doJet = !doJet } // The falling rock has stopped StopRock(m) stateAndSleep(m) rockType = (rockType + 1) % rocks } fmt.Println("After", numRocks, "the tower is", GetHeight(m), "blocks tall") } func GetState(r, j int, m *h.GrowUpCoordByteMap) (string, int) { ret := fmt.Sprintf("%d;%d;", r, j) for x := 1; x < 7; x++ { for y := m.TLY; y > 1; y-- { if m.Get(h.Coordinate{X: x, Y: y}) == '#' { ret = fmt.Sprintf("%s-%d", ret, m.TLY-y) break } } } return ret, GetHeight(m) } func GetHeight(m *h.GrowUpCoordByteMap) int { return h.GetHighestY(m.FindAll('#')...) } func FindTopAtRestY(m *h.GrowUpCoordByteMap) int { rockSpots := m.FindAll('#') if len(rockSpots) != 0 { return h.GetHighestY(rockSpots...) } return 0 } func StopRock(m *h.GrowUpCoordByteMap) { rockSpots := m.FindAll('@') for i := range rockSpots { m.Put(rockSpots[i], '#') } } func ShiftRock(dir int, m *h.GrowUpCoordByteMap) { if !CanShift(dir, m) { return } rockSpots := m.FindAll('@') for i := range rockSpots { m.Put(rockSpots[i], ' ') } for i := range rockSpots { switch dir { case dirU: m.Put(GetUp(rockSpots[i]), '@') case dirR: m.Put(GetRight(rockSpots[i]), '@') case dirD: m.Put(GetDown(rockSpots[i]), '@') case dirL: m.Put(GetLeft(rockSpots[i]), '@') } } } func CanShift(dir int, m *h.GrowUpCoordByteMap) bool { rockSpots := m.FindAll('@') switch dir { case dirU: // Shouldn't need to, though. return true case dirR: for i := range rockSpots { tst := m.Get(GetRight(rockSpots[i])) if tst != '@' && tst != ' ' { return false } } return true case dirD: for i := range rockSpots { tst := m.Get(GetDown(rockSpots[i])) if tst != '@' && tst != ' ' { return false } } return true case dirL: for i := range rockSpots { tst := m.Get(GetLeft(rockSpots[i])) if tst != '@' && tst != ' ' { return false } } return true } return false } func AddFallingRock(tp int, m *h.GrowUpCoordByteMap) { rock := GetRockBytes(tp) pos := h.Coordinate{X: 3, Y: FindTopAtRestY(m) + 4} for pos.Y+3 > m.TLY { m.PutBytes([][]byte{emptyRow}, h.Coordinate{X: 0, Y: m.TLY + 1}) } m.PutBytes(rock, pos) } func AtRest(m *h.GrowUpCoordByteMap) bool { rockSpots := m.FindAll('@') if len(rockSpots) == 0 { return true } for i := range rockSpots { pos := GetDown(rockSpots[i]) wrk := m.Get(pos) if pos.Y == m.BRY { return true } if wrk != ' ' && wrk != '@' { return true } } return false } func GetRockBytes(tp int) [][]byte { switch tp { case 0: return [][]byte{{'@', '@', '@', '@'}} case 1: return [][]byte{ {' ', '@', ' '}, {'@', '@', '@'}, {' ', '@', ' '}, } case 2: return [][]byte{ {'@', '@', '@'}, {' ', ' ', '@'}, {' ', ' ', '@'}, } case 3: return [][]byte{ {'@'}, {'@'}, {'@'}, {'@'}, } case 4: return [][]byte{ {'@', '@'}, {'@', '@'}, } } return [][]byte{} } func GetLeft(c h.Coordinate) h.Coordinate { return h.Coordinate{X: c.X - 1, Y: c.Y} } func GetRight(c h.Coordinate) h.Coordinate { return h.Coordinate{X: c.X + 1, Y: c.Y} } func GetUp(c h.Coordinate) h.Coordinate { return h.Coordinate{X: c.X, Y: c.Y + 1} } func GetDown(c h.Coordinate) h.Coordinate { return h.Coordinate{X: c.X, Y: c.Y - 1} } func DirToString(shiftDir int) string { switch shiftDir { case dirU: return "^" case dirR: return ">" case dirD: return "v" case dirL: return "<" } return " " }