2022 Complete

This commit is contained in:
2023-05-30 12:52:47 -05:00
parent 562faef625
commit ee4f4626f9
2 changed files with 551 additions and 523 deletions

View File

@@ -3,420 +3,400 @@ package main
import (
"fmt"
"math"
"strconv"
"strings"
h "git.bullercodeworks.com/brian/adventofcode/helpers"
)
func main() {
inp := h.StdinToStringSlice()
//part1(inp)
part2(inp)
}
func part1(inp []string) {
board, inst := inp[:len(inp)-2], inp[len(inp)-1]
b := BuildBoard(board)
var count int
var turn byte
fmt.Println(b)
fmt.Println(b.pos)
for len(inst) > 0 {
count, turn, inst = getNextInstructions(inst)
b.Move(count)
if turn == 'L' || turn == 'R' {
b.Turn(turn)
}
}
fmt.Println("Password:", b.Password())
lines := h.StdinToStringSlice()
path := lines[len(lines)-1]
m := lines[:len(lines)-2]
p1 := NewFlatPuzzle(m)
s1 := NewSolver(p1, path)
fmt.Println(s1.Password())
p2 := NewCubePuzzle(m)
s2 := NewSolver(p2, path)
fmt.Println(s2.Password())
}
func part2(inp []string) {
board, inst := inp[:len(inp)-2], inp[len(inp)-1]
b := BuildCubeBoard(board)
var count int
var turn byte
fmt.Println(b)
fmt.Println(b.pos)
for len(inst) > 0 {
count, turn, inst = getNextInstructions(inst)
b.Move(count)
if turn == 'L' || turn == 'R' {
b.Turn(turn)
}
}
fmt.Println("Password:", b.Password())
type Solver struct {
puzzle Puzzle
path string
coordinate Coordinate
facing Direction
}
func getNextInstructions(inst string) (int, byte, string) {
for i := range inst {
if inst[i] == 'L' || inst[i] == 'R' {
return h.Atoi(inst[:i]), inst[i], inst[i+1:]
func NewSolver(puzzle Puzzle, path string) *Solver {
solver := Solver{
puzzle: puzzle,
path: path,
coordinate: puzzle.TopLeft(),
facing: RIGHT,
}
return &solver
}
func (s *Solver) Password() int {
s.coordinate = s.puzzle.TopLeft()
path := s.path
for len(path) > 0 {
var i int
if path[0] == 'R' || path[0] == 'L' {
i = 1
} else {
i = strings.IndexAny(path, "RL")
}
if i < 0 {
i = len(path)
}
s.Do(path[:i])
path = path[i:]
}
return 1000*(s.coordinate.Y+1) + 4*(s.coordinate.X+1) + int(s.facing)
}
func (s *Solver) Do(ins string) {
if ins == "R" {
s.facing = (s.facing + 1) % 4
return
}
if ins == "L" {
s.facing = (s.facing + 3) % 4
return
}
steps, err := strconv.Atoi(ins)
if err != nil {
panic(err)
}
for steps > 0 {
c, d := s.puzzle.Next(s.coordinate, s.facing)
if s.puzzle.TileAt(c) == WALL {
break
}
s.coordinate = c
s.facing = d
steps--
}
}
type Puzzle interface {
TileAt(Coordinate) Tile
Next(Coordinate, Direction) (Coordinate, Direction)
TopLeft() Coordinate
}
type FlatPuzzle struct {
tiles map[Coordinate]Tile
col map[int][2]int
row map[int][2]int
}
func NewFlatPuzzle(s []string) *FlatPuzzle {
puzzle := FlatPuzzle{
tiles: make(map[Coordinate]Tile),
col: make(map[int][2]int),
row: make(map[int][2]int),
}
for y, line := range s {
for x, c := range line {
switch c {
case '.':
puzzle.tiles[Coordinate{x, y}] = OPEN
case '#':
puzzle.tiles[Coordinate{x, y}] = WALL
default:
continue
}
if _, ok := puzzle.col[x]; !ok {
puzzle.col[x] = [2]int{math.MaxInt, 0}
}
if y < puzzle.col[x][0] {
puzzle.col[x] = [2]int{y, puzzle.col[x][1]}
}
if y > puzzle.col[x][1] {
puzzle.col[x] = [2]int{puzzle.col[x][0], y}
}
if _, ok := puzzle.row[y]; !ok {
puzzle.row[y] = [2]int{math.MaxInt, 0}
}
if x < puzzle.row[y][0] {
puzzle.row[y] = [2]int{x, puzzle.row[y][1]}
}
if x > puzzle.row[y][1] {
puzzle.row[y] = [2]int{puzzle.row[y][0], x}
}
}
}
return h.Atoi(inst), 0, ""
return &puzzle
}
func (puzzle *FlatPuzzle) TileAt(c Coordinate) Tile {
return puzzle.tiles[c]
}
func (puzzle *FlatPuzzle) TopLeft() Coordinate {
return Coordinate{puzzle.row[0][0], 0}
}
func (puzzle *FlatPuzzle) Next(c Coordinate, d Direction) (Coordinate, Direction) {
c.X += directions[d][0]
c.Y += directions[d][1]
if _, ok := puzzle.tiles[c]; ok {
return c, d
}
switch d {
case RIGHT:
c.X = puzzle.row[c.Y][0]
case DOWN:
c.Y = puzzle.col[c.X][0]
case LEFT:
c.X = puzzle.row[c.Y][1]
case UP:
c.Y = puzzle.col[c.X][1]
}
return c, d
}
type CubePuzzle struct {
tiles map[Coordinate]Tile
mapping map[Coordinate]*Square
squares [6]*Square
sidelen int
}
func NewCubePuzzle(s []string) *CubePuzzle {
var squares [6]*Square
tiles := make(map[Coordinate]Tile)
mapping := make(map[Coordinate]*Square)
w, h := 0, len(s)
for _, line := range s {
if len(line) > w {
w = len(line)
}
}
var sidelen int
switch {
case h/5 == w/2 && h%5 == 0 && w%2 == 0:
sidelen = h / 5
case h/2 == w/5 && h%2 == 0 && w%5 == 0:
sidelen = h / 2
case h/3 == w/4 && h%3 == 0 && w%4 == 0:
sidelen = h / 3
case h/4 == w/3 && h%4 == 0 && w%3 == 0:
sidelen = h / 4
default:
panic("not a valid cube net")
}
i := 0
for y, line := range s {
for x, c := range line {
coordinate := Coordinate{x, y}
switch c {
case '.':
tiles[coordinate] = OPEN
case '#':
tiles[coordinate] = WALL
default:
continue
}
if x%sidelen == 0 && y%sidelen == 0 {
for squares[i] != nil {
i++
}
s := NewSquare(i, Coordinate{x, y}, sidelen)
squares[i] = s
mapping[coordinate] = squares[i]
} else {
mapping[coordinate] = mapping[Coordinate{x - (x % sidelen), y - (y % sidelen)}]
}
}
}
for _, s := range squares {
if a, ok := mapping[Coordinate{s.edges[RIGHT].p1.X + 1, s.edges[RIGHT].p1.Y}]; ok {
s.edges[RIGHT].adjacent = a.edges[LEFT]
a.edges[LEFT].adjacent = s.edges[RIGHT]
}
if a, ok := mapping[Coordinate{s.edges[DOWN].p1.X, s.edges[DOWN].p1.Y + 1}]; ok {
s.edges[DOWN].adjacent = a.edges[UP]
a.edges[UP].adjacent = s.edges[DOWN]
}
}
queue := []*SquareEdge{}
for _, s := range squares {
for _, e := range s.edges {
if e.adjacent == nil {
queue = append(queue, e)
}
}
}
for len(queue) > 0 {
e := queue[0]
queue = queue[1:]
if e.adjacent != nil {
continue
}
a := e.square.edges[(e.direction+1)%4].adjacent
if a == nil {
queue = append(queue, e)
continue
}
a = a.square.edges[(a.direction+1)%4].adjacent
if a == nil {
queue = append(queue, e)
continue
}
a = a.square.edges[(a.direction+1)%4]
e.adjacent = a
a.adjacent = e
}
puzzle := CubePuzzle{
tiles: tiles,
mapping: mapping,
squares: squares,
sidelen: sidelen,
}
return &puzzle
}
func (puzzle *CubePuzzle) TileAt(c Coordinate) Tile {
return puzzle.tiles[c]
}
func (puzzle *CubePuzzle) TopLeft() Coordinate {
c := puzzle.squares[0].edges[0].p1
for _, s := range puzzle.squares {
if s.edges[UP].p1.Y == 0 && s.edges[UP].p1.X < c.X {
c = s.edges[UP].p1
}
}
return c
}
func (puzzle *CubePuzzle) Next(c Coordinate, d Direction) (Coordinate, Direction) {
s := puzzle.mapping[c]
c.X += directions[d][0]
c.Y += directions[d][1]
if dst, ok := puzzle.mapping[c]; ok && dst == s {
return c, d
}
e := s.edges[d]
a := e.adjacent
var n int
if d == LEFT || d == RIGHT {
n = c.Y - e.p1.Y
} else {
n = c.X - e.p1.X
}
if (e.direction/2 != a.direction/2 && e.direction%2 != a.direction%2) || e.direction == a.direction {
n = puzzle.sidelen - 1 - n
}
c = a.NthCoordinate(n)
d = (a.direction + 2) % 4
return c, d
}
type Square struct {
id int
edges [4]*SquareEdge
}
func NewSquare(id int, c Coordinate, sidelen int) *Square {
square := Square{id: id}
var d Direction
for d = RIGHT; d <= UP; d++ {
var p1, p2 Coordinate
switch d {
case RIGHT:
p1 = Coordinate{c.X + sidelen - 1, c.Y}
p2 = Coordinate{c.X + sidelen - 1, c.Y + sidelen - 1}
case DOWN:
p1 = Coordinate{c.X, c.Y + sidelen - 1}
p2 = Coordinate{c.X + sidelen - 1, c.Y + sidelen - 1}
case LEFT:
p1 = c
p2 = Coordinate{c.X, c.Y + sidelen - 1}
case UP:
p1 = c
p2 = Coordinate{c.X + sidelen - 1, c.Y}
}
edge := SquareEdge{
p1: p1,
p2: p2,
square: &square,
direction: d,
}
square.edges[d] = &edge
}
return &square
}
type SquareEdge struct {
p1, p2 Coordinate
square *Square
direction Direction
adjacent *SquareEdge
}
func (edge *SquareEdge) NthCoordinate(n int) Coordinate {
c := edge.p1
if edge.direction == UP || edge.direction == DOWN {
c.X += n
} else {
c.Y += n
}
return c
}
type Direction int
const (
dirR = iota
dirD
dirL
dirU
dirErr
sideTop = iota
sideBottom
sideRight
sideLeft
sideFront
sideBack
RIGHT Direction = iota
DOWN
LEFT
UP
)
type CubeBoard struct {
Top *CubeBoardSide
Bottom *CubeBoardSide
Left *CubeBoardSide
Right *CubeBoardSide
Front *CubeBoardSide
Back *CubeBoardSide
// Pos now holds:
// y, x, dir, side
pos []int
var directions [4][2]int = [4][2]int{
{1, 0},
{0, 1},
{-1, 0},
{0, -1},
}
// For the purposes of directions, we treat the board like this:
// [T]
// [B][L][F][R]
// [B]
// So up from left, front, back, and right are all top
// Up from bottom is front
// Down from bottom is back
// Up from top is back
// Down from top is front
// So you can continually go left on the main level and circumnavigate the cube
// Continually going up from anywhere will eventually get you bouncing between top and back
// Continually going down from anywhere will eventually get you bouncing between bottom and back
func BuildCubeBoard(inp []string) *CubeBoard {
cb := CubeBoard{}
var side string
for i := 0; i < len(inp); i++ {
if len(inp) == 0 {
break
}
side = inp[i]
i++
cbs := BuildCubeBoardSide(inp[i : i+len(inp[i])])
switch side {
case "Top":
cb.Top = cbs
case "Bottom":
cb.Bottom = cbs
case "Left":
cb.Left = cbs
case "Right":
cb.Right = cbs
case "Front":
cb.Front = cbs
case "Back":
cb.Back = cbs
}
i = i + cbs.Size() - 1
}
cb.pos = []int{0, 0, dirR, sideTop}
return &cb
}
func (cb *CubeBoard) Size() int { return cb.Top.Size() }
func (cb *CubeBoard) Move(count int) {
}
func (cb *CubeBoard) GetPosUp() []int {
y := cb.pos[0]
x := cb.pos[1]
dir := cb.pos[2]
side := cb.pos[3]
if y == 0 {
// Going to another side
switch side {
case sideTop:
side = sideBack
case sideBottom:
side = sideFront
case sideFront:
side = sideTop
case sideBack:
side = sideTop
case sideLeft:
side = sideTop
case sideRight:
side = sideTop
}
}
return []int{y, x, dir, side}
}
func (cb *CubeBoard) GetPosDown() []int {
y := cb.pos[0]
x := cb.pos[1]
dir := cb.pos[2]
side := cb.pos[3]
if y == cb.Size()-1 {
// Going to another side
switch side {
case sideTop:
side = sideFront
case sideBottom:
side = sideBack
case sideFront:
side = sideBottom
case sideBack:
side = sideBottom
case sideLeft:
side = sideBottom
case sideRight:
side = sideBottom
}
} else {
y = y + 1
}
return []int{y, x, dir, side}
}
func (cb *CubeBoard) GetPosRight() []int {
y := cb.pos[0]
x := cb.pos[1]
dir := cb.pos[2]
side := cb.pos[3]
if x == cb.Size()-1 {
// Going to another side
switch side {
case sideTop:
side = sideRight
case sideBottom:
side = sideRight
case sideFront:
side = sideRight
case sideBack:
side = sideLeft
case sideLeft:
side = sideFront
case sideRight:
side = sideBack
}
} else {
y = y + 1
}
return []int{y, x, dir, side}
type Coordinate struct {
X, Y int
}
// Get the current pos' side
func (cb *CubeBoard) Side() *CubeBoardSide {
switch cb.pos[3] {
case sideTop:
return cb.Top
case sideBottom:
return cb.Bottom
case sideFront:
return cb.Front
case sideBack:
return cb.Back
case sideLeft:
return cb.Left
case sideRight:
return cb.Right
}
return nil
}
func (cb *CubeBoard) Turn(dir byte) {
if dir == 'R' {
cb.pos[2] = (cb.pos[2] + 1) % dirErr
} else if dir == 'L' {
cb.pos[2] = (cb.pos[2] - 1 + dirErr) % dirErr
}
}
func (cb *CubeBoard) Tile(y, x, side int) byte {
switch side {
case sideTop:
return cb.Top.Tile(y, x)
case sideBottom:
return cb.Bottom.Tile(y, x)
case sideLeft:
return cb.Left.Tile(y, x)
case sideRight:
return cb.Right.Tile(y, x)
case sideFront:
return cb.Front.Tile(y, x)
case sideBack:
return cb.Back.Tile(y, x)
}
return 0
}
func (cb *CubeBoard) Password() int {
return 0
}
type Tile bool
type CubeBoardSide struct {
spots [][]byte
}
func BuildCubeBoardSide(inp []string) *CubeBoardSide {
cbs := CubeBoardSide{}
fmt.Println("Building CBS from:", inp)
cbs.spots = make([][]byte, len(inp))
for y := range inp {
row := make([]byte, len(inp[y]))
for x := range inp[y] {
row[x] = inp[y][x]
}
cbs.spots[y] = row
}
return &cbs
}
func (cbs *CubeBoardSide) Size() int {
return len(cbs.spots)
}
func (cbs *CubeBoardSide) Tile(y, x int) byte {
if len(cbs.spots) < y && len(cbs.spots[y]) < x {
return cbs.spots[y][x]
}
return 0
}
type Board struct {
spots [][]byte
// pos holds
// y, x, dir
pos []int
}
func BuildBoard(inp []string) *Board {
maxX := math.MinInt
for y := range inp {
maxX = h.Max(maxX, len(inp[y]))
}
b := Board{
spots: make([][]byte, len(inp)),
}
for y := range inp {
sl := make([]byte, maxX)
for x := range inp[y] {
sl[x] = inp[y][x]
}
b.spots[y] = sl
}
b.pos = make([]int, 3)
for x := range b.spots[0] {
if b.spots[0][x] == '.' {
b.pos = []int{0, x, dirR}
break
}
}
return &b
}
func (b *Board) Password() int {
return ((b.pos[0] + 1) * 1000) + ((b.pos[1] + 1) * 4) + b.pos[2]
}
func (b *Board) Tile(y, x int) byte {
if len(b.spots) > y && len(b.spots[y]) > x {
return b.spots[y][x]
}
return 0
}
func (b *Board) GetNextPos() []int {
switch b.pos[2] {
case dirU:
return b.GetPosUp()
case dirR:
return b.GetPosRight()
case dirD:
return b.GetPosDown()
case dirL:
return b.GetPosLeft()
}
return []int{-1, -1}
}
func (b *Board) GetPosUp() []int {
x := b.pos[1]
y := ((b.pos[0] - 1) + len(b.spots)) % len(b.spots)
for (b.spots[y][x] == 0 || b.spots[y][x] == 32) && y != b.pos[0] {
y = ((y - 1) + len(b.spots)) % len(b.spots)
}
return []int{y, x}
}
func (b *Board) GetPosRight() []int {
y := b.pos[0]
x := (b.pos[1] + 1) % len(b.spots[y])
for (b.spots[y][x] == 0 || b.spots[y][x] == 32) && x != b.pos[1] {
x = (x + 1) % len(b.spots[y])
}
return []int{y, x}
}
func (b *Board) GetPosDown() []int {
x := b.pos[1]
y := (b.pos[0] + 1) % len(b.spots)
for (b.spots[y][x] == 0 || b.spots[y][x] == 32) && y != b.pos[0] {
y = (y + 1) % len(b.spots)
}
return []int{y, x}
}
func (b *Board) GetPosLeft() []int {
y := b.pos[0]
x := ((b.pos[1] - 1) + len(b.spots[y])) % len(b.spots[y])
for (b.spots[y][x] == 0 || b.spots[y][x] == 32) && x != b.pos[1] {
x = ((x - 1) + len(b.spots[y])) % len(b.spots[y])
}
return []int{y, x}
}
func (b *Board) Move(count int) {
var to []int
for count > 0 {
switch b.pos[2] {
case dirU:
to = b.GetPosUp()
case dirR:
to = b.GetPosRight()
case dirD:
to = b.GetPosDown()
case dirL:
to = b.GetPosLeft()
}
if b.Tile(to[0], to[1]) == '.' {
count--
b.pos[0], b.pos[1] = to[0], to[1]
} else {
break
}
}
}
func (b *Board) Turn(dir byte) {
if dir == 'R' {
b.pos[2] = (b.pos[2] + 1) % dirErr
} else if dir == 'L' {
b.pos[2] = (b.pos[2] - 1 + dirErr) % dirErr
}
}
func (b Board) String() string {
var ret string
for y := range b.spots {
for x := range b.spots[y] {
bt := b.spots[y][x]
if b.pos[1] == x && b.pos[0] == y {
switch b.pos[2] {
case dirU:
bt = '^'
case dirR:
bt = '>'
case dirD:
bt = 'v'
case dirL:
bt = '<'
default:
bt = '!'
}
}
ret = ret + fmt.Sprint(string(bt))
}
ret = ret + "\n"
}
return ret
}
const (
OPEN Tile = true
WALL Tile = false
)