403 lines
7.3 KiB
Go
403 lines
7.3 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"math"
|
|
"strconv"
|
|
"strings"
|
|
|
|
h "git.bullercodeworks.com/brian/adventofcode/helpers"
|
|
)
|
|
|
|
func main() {
|
|
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())
|
|
}
|
|
|
|
type Solver struct {
|
|
puzzle Puzzle
|
|
path string
|
|
coordinate Coordinate
|
|
facing Direction
|
|
}
|
|
|
|
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 &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 (
|
|
RIGHT Direction = iota
|
|
DOWN
|
|
LEFT
|
|
UP
|
|
)
|
|
|
|
var directions [4][2]int = [4][2]int{
|
|
{1, 0},
|
|
{0, 1},
|
|
{-1, 0},
|
|
{0, -1},
|
|
}
|
|
|
|
type Coordinate struct {
|
|
X, Y int
|
|
}
|
|
|
|
type Tile bool
|
|
|
|
const (
|
|
OPEN Tile = true
|
|
WALL Tile = false
|
|
)
|