217 lines
4.8 KiB
Go
217 lines
4.8 KiB
Go
package day14
|
|
|
|
import (
|
|
"fmt"
|
|
"log"
|
|
"os"
|
|
"strings"
|
|
)
|
|
|
|
func Run() int {
|
|
fmt.Println("hello day 14")
|
|
|
|
field := ReadPlatform("day14/input")
|
|
fmt.Println(field.String())
|
|
|
|
// fmt.Printf("> lines for field %+v\n", field.UpIndices())
|
|
// field.Move(field.Height(), field.UpIndices())
|
|
cycles := 1000000000
|
|
states := make(map[string]int)
|
|
// 2023/12/14 11:50:32 >>> found loop. known state after 10 equal to one after 3
|
|
|
|
var loopLen, initialStretch int
|
|
|
|
for i := 1; i <= cycles; i++ {
|
|
field.DoSpinCycle()
|
|
// fmt.Println(field.String())
|
|
|
|
stringRepr := field.String()
|
|
prevIter, known := states[stringRepr]
|
|
if known {
|
|
log.Printf(">>> found loop. known state after %d equal to one after %d", i, prevIter)
|
|
initialStretch = prevIter
|
|
loopLen = i - prevIter
|
|
break
|
|
}
|
|
|
|
states[stringRepr] = i
|
|
|
|
if i % 100000 == 0 {
|
|
log.Print("done ", i, " cycles")
|
|
}
|
|
}
|
|
|
|
// field is already in a 'loop' state.
|
|
// so we've already done 'initial stretch' so to make field in same state as after 'cycles'
|
|
// i only need to check rest of (cycles - initialStretch)
|
|
movesToMake := (cycles - initialStretch)%loopLen
|
|
log.Printf(">>> data: initial steps %d, loop len %d. to do same as %d iterations i need %d", initialStretch, loopLen, cycles, movesToMake)
|
|
|
|
for i := 1; i <= movesToMake; i++ {
|
|
field.DoSpinCycle()
|
|
// fmt.Println(field.String())
|
|
}
|
|
|
|
// north rock load
|
|
return field.NorthLoad()
|
|
}
|
|
|
|
const Rock rune = 'O'
|
|
const Wall rune = '#'
|
|
const Space rune = '.'
|
|
|
|
type Platform struct {
|
|
Rocks [][]rune
|
|
}
|
|
|
|
func (p *Platform) Height() int {
|
|
return len(p.Rocks)
|
|
}
|
|
func (p *Platform) Width() int {
|
|
return len(p.Rocks[0])
|
|
}
|
|
|
|
func ReadPlatform(filename string) Platform {
|
|
bytes, err := os.ReadFile(filename)
|
|
if err != nil {
|
|
panic(fmt.Sprint("cannot read file: ", filename))
|
|
}
|
|
text := string(bytes)
|
|
text = strings.TrimSpace(text)
|
|
lines := strings.Split(text, "\n")
|
|
|
|
rocks := make([][]rune, len(lines))
|
|
for i, line := range lines {
|
|
rocks[i] = []rune(line)
|
|
}
|
|
|
|
return Platform{
|
|
Rocks: rocks,
|
|
}
|
|
}
|
|
|
|
func (p *Platform) String() string {
|
|
text := "\n"
|
|
for _, row := range p.Rocks {
|
|
text += string(row)
|
|
text += "\n"
|
|
}
|
|
|
|
return text
|
|
}
|
|
|
|
type Coord struct{ Row, Col int }
|
|
|
|
// indices for moving UP, from down to up
|
|
func (p *Platform) UpIndices() [][]Coord {
|
|
lines := make([][]Coord, 0)
|
|
for col := 0; col < p.Width(); col++ {
|
|
line := make([]Coord, 0)
|
|
for row := 0; row < p.Height(); row++ {
|
|
line = append(line, Coord{Row: row, Col: col})
|
|
}
|
|
lines = append(lines, line)
|
|
}
|
|
return lines
|
|
}
|
|
|
|
// indices for moving DOWN, from up to down
|
|
func (p *Platform) DownIndices() [][]Coord {
|
|
lines := make([][]Coord, 0)
|
|
for col := 0; col < p.Width(); col++ {
|
|
line := make([]Coord, 0)
|
|
for row := p.Height() - 1; row >= 0; row-- {
|
|
line = append(line, Coord{Row: row, Col: col})
|
|
}
|
|
lines = append(lines, line)
|
|
}
|
|
return lines
|
|
}
|
|
|
|
// indices for moving RIGHT from right to left
|
|
func (p *Platform) RightIndices() [][]Coord {
|
|
lines := make([][]Coord, 0)
|
|
for row := 0; row < p.Height(); row++ {
|
|
line := make([]Coord, 0)
|
|
for col := p.Width() - 1; col >= 0; col-- {
|
|
line = append(line, Coord{Row: row, Col: col})
|
|
}
|
|
lines = append(lines, line)
|
|
}
|
|
return lines
|
|
}
|
|
|
|
// indices for moving LEFT, from left to right
|
|
func (p *Platform) LeftIndices() [][]Coord {
|
|
lines := make([][]Coord, 0)
|
|
for row := 0; row < p.Height(); row++ {
|
|
line := make([]Coord, 0)
|
|
for col := 0; col < p.Width(); col++ {
|
|
line = append(line, Coord{Row: row, Col: col})
|
|
}
|
|
lines = append(lines, line)
|
|
}
|
|
return lines
|
|
}
|
|
|
|
func (p *Platform) SymbAt(coord Coord) rune {
|
|
return p.Rocks[coord.Row][coord.Col]
|
|
}
|
|
func (p *Platform) SetSymbAt(coord Coord, symb rune) {
|
|
p.Rocks[coord.Row][coord.Col] = symb
|
|
}
|
|
|
|
func (p *Platform) Move(n int, lines [][]Coord) {
|
|
for _, line := range lines {
|
|
moveSize := 0
|
|
for i, coord := range line {
|
|
symb := p.SymbAt(coord)
|
|
switch symb {
|
|
case Space:
|
|
moveSize += 1
|
|
if moveSize > n {
|
|
moveSize = n
|
|
}
|
|
case Wall:
|
|
moveSize = 0
|
|
case Rock:
|
|
if moveSize == 0 {
|
|
continue
|
|
}
|
|
// get coord for moveSize back. and set that to 'o'
|
|
// and set current to '.'
|
|
// panic if that place is not '.' i guess
|
|
moveTo := line[i-moveSize]
|
|
symbAtTarget := p.SymbAt(moveTo)
|
|
if symbAtTarget != Space {
|
|
panic(fmt.Sprintf("attempting to move %+v to %+v, target symbol is %s, not '.'",
|
|
coord, moveTo, string(symbAtTarget)))
|
|
}
|
|
p.SetSymbAt(moveTo, Rock)
|
|
p.SetSymbAt(coord, Space)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func (p *Platform) NorthLoad() int {
|
|
total := 0
|
|
height := p.Height()
|
|
for i, row := range p.Rocks {
|
|
for _, symb := range row {
|
|
if symb == Rock {
|
|
total += (height - i)
|
|
}
|
|
}
|
|
}
|
|
return total
|
|
}
|
|
|
|
func (p *Platform) DoSpinCycle() {
|
|
// north, west, south, east - till the end
|
|
p.Move(p.Height(), p.UpIndices())
|
|
p.Move(p.Width(), p.LeftIndices())
|
|
p.Move(p.Height(), p.DownIndices())
|
|
p.Move(p.Width(), p.RightIndices())
|
|
}
|