adventofcode/2022/day17/main.go

295 lines
6.0 KiB
Go

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 " "
}