2022 Complete
This commit is contained in:
parent
562faef625
commit
ee4f4626f9
@ -1,143 +1,191 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"log"
|
||||||
|
|
||||||
h "git.bullercodeworks.com/brian/adventofcode/helpers"
|
h "git.bullercodeworks.com/brian/adventofcode/helpers"
|
||||||
)
|
)
|
||||||
|
|
||||||
var output []string
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
inp := h.StdinToStringSlice()
|
|
||||||
part1(inp)
|
|
||||||
part2(inp)
|
|
||||||
}
|
|
||||||
|
|
||||||
func showLog() {
|
|
||||||
fmt.Println(h.CLEAR_SCREEN)
|
|
||||||
fmt.Println(strings.Join(output, "\n"))
|
|
||||||
}
|
|
||||||
|
|
||||||
func setLog(v ...string) {
|
|
||||||
output = []string{}
|
|
||||||
log(v...)
|
|
||||||
}
|
|
||||||
func log(v ...string) {
|
|
||||||
output = append(output, v...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func loadMonkeys(inp []string) map[string]*Monkey {
|
|
||||||
monkeys := make(map[string]*Monkey)
|
|
||||||
for _, v := range inp {
|
|
||||||
if len(v) == 0 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
m := NewMonkey(strings.Split(v, ": ")...)
|
|
||||||
monkeys[m.name] = m
|
|
||||||
}
|
|
||||||
for _, v := range monkeys {
|
|
||||||
v.init(monkeys, 1)
|
|
||||||
}
|
|
||||||
return monkeys
|
|
||||||
}
|
|
||||||
|
|
||||||
func part1(inp []string) {
|
|
||||||
monkeys := loadMonkeys(inp)
|
|
||||||
log("# Part 1", fmt.Sprintf("%d", monkeys["root"].Value()))
|
|
||||||
showLog()
|
|
||||||
}
|
|
||||||
|
|
||||||
func part2(inp []string) {
|
|
||||||
fmt.Println()
|
|
||||||
monkeys := loadMonkeys(inp)
|
|
||||||
|
|
||||||
m1 := monkeys["root"].inp1 // <- Human is in here
|
|
||||||
m2 := monkeys["root"].inp2 // <- We need to make it match this
|
|
||||||
monkeys["humn"].Value = func() int { return 0 }
|
|
||||||
fmt.Println(m1.Value(), m2.Value())
|
|
||||||
monkeys["humn"].Value = func() int { return 1000 }
|
|
||||||
fmt.Println(m1.Value(), m2.Value())
|
|
||||||
//v := monkeys["root"].Value()
|
|
||||||
|
|
||||||
/*
|
|
||||||
i := 0
|
|
||||||
var lastV int
|
|
||||||
for {
|
|
||||||
monkeys["humn"].Value = func() int {
|
|
||||||
return i
|
|
||||||
}
|
|
||||||
v := monkeys["root"].Value()
|
|
||||||
if v == 0 {
|
|
||||||
output = append(output[:1], []string{"# Part 2", fmt.Sprintf("Found: %d", i)}...)
|
|
||||||
showLog()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
output = append(output[:3], []string{
|
|
||||||
"# Part 2",
|
|
||||||
fmt.Sprintf("Scanning... [ humn: %d ]", i),
|
|
||||||
fmt.Sprintf("CURR: %d =? 0", v),
|
|
||||||
fmt.Sprintf("LAST: %d", lastV),
|
|
||||||
}...)
|
|
||||||
setLog(output...)
|
|
||||||
showLog()
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
|
|
||||||
type Operation int
|
|
||||||
|
|
||||||
const (
|
|
||||||
opAdd = iota
|
|
||||||
opSub
|
|
||||||
opMlt
|
|
||||||
opDiv
|
|
||||||
)
|
|
||||||
|
|
||||||
type Monkey struct {
|
type Monkey struct {
|
||||||
name string
|
name string
|
||||||
op Operation
|
yelled bool
|
||||||
inp1, inp2 *Monkey
|
value int
|
||||||
rawValue string
|
m1, m2 *Monkey
|
||||||
Value func() int
|
op string
|
||||||
|
name1, name2 string
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewMonkey(inp ...string) *Monkey { return &Monkey{name: inp[0], rawValue: inp[1]} }
|
var monkeys map[string]*Monkey
|
||||||
func (m *Monkey) init(monkeys map[string]*Monkey, part int) {
|
var verbose bool
|
||||||
pts := strings.Fields(m.rawValue)
|
var humanval int
|
||||||
if len(pts) == 1 {
|
|
||||||
m.Value = func() int { return h.Atoi(pts[0]) }
|
func main() {
|
||||||
|
partOne := flag.Bool("1", false, "run exercise part1, (default: part2)")
|
||||||
|
verboseFlag := flag.Bool("v", false, "verbose: print extra info")
|
||||||
|
humanFlag := flag.Int("h", -1, "set value of \"humn\" monkey, return delta")
|
||||||
|
flag.Parse()
|
||||||
|
verbose = *verboseFlag
|
||||||
|
humanval = *humanFlag
|
||||||
|
lines := h.StdinToStringSlice()
|
||||||
|
parse(lines)
|
||||||
|
|
||||||
|
var result int
|
||||||
|
if *partOne {
|
||||||
|
vprint("Running Part 1")
|
||||||
|
result = part1(lines)
|
||||||
} else {
|
} else {
|
||||||
if part == 2 && m.name == "root" {
|
vprint("Running Part 2")
|
||||||
pts[1] = "-"
|
result = part2(lines)
|
||||||
}
|
}
|
||||||
m.inp1, m.inp2 = monkeys[pts[0]], monkeys[pts[2]]
|
fmt.Println(result)
|
||||||
switch pts[1] {
|
}
|
||||||
case "+":
|
|
||||||
m.Value = func() int {
|
func part1(lines []string) int {
|
||||||
return m.inp1.Value() + m.inp2.Value()
|
return listenCached(monkeys["root"])
|
||||||
|
}
|
||||||
|
|
||||||
|
func part2(lines []string) int {
|
||||||
|
root := monkeys["root"]
|
||||||
|
delta := Monkey{name: "delta", m1: root.m1, m2: root.m2, name1: root.name1, name2: root.name2, op: "-"}
|
||||||
|
if humanval != -1 {
|
||||||
|
human := monkeys["humn"]
|
||||||
|
human.yelled = true
|
||||||
|
human.value = humanval
|
||||||
|
return listenCached(&delta)
|
||||||
}
|
}
|
||||||
case "-":
|
// We find the value of human making delta == 0 by dichotomy
|
||||||
m.Value = func() int {
|
return humanZeroing(&delta)
|
||||||
return m.inp1.Value() - m.inp2.Value()
|
}
|
||||||
}
|
|
||||||
case "*":
|
func parse(lines []string) {
|
||||||
m.Value = func() int {
|
var name, name1, op, name2 string
|
||||||
return m.inp1.Value() * m.inp2.Value()
|
var num int
|
||||||
}
|
monkeys = make(map[string]*Monkey, 0)
|
||||||
case "/":
|
// 1st pass, gather names
|
||||||
m.Value = func() int {
|
for _, line := range lines {
|
||||||
return m.inp1.Value() / m.inp2.Value()
|
if n, _ := fmt.Sscanf(line, "%s %d", &name, &num); n == 2 {
|
||||||
}
|
m := Monkey{name: name[:len(name)-1], value: num, yelled: true}
|
||||||
case "=":
|
monkeys[name[:len(name)-1]] = &m
|
||||||
m.Value = func() int {
|
} else if n, _ = fmt.Sscanf(line, "%s %s %s %s", &name, &name1, &op, &name2); n == 4 {
|
||||||
if m.inp1.Value() == m.inp2.Value() {
|
m := Monkey{name: name[:len(name)-1], op: op, name1: name1, name2: name2}
|
||||||
return 1
|
monkeys[name[:len(name)-1]] = &m
|
||||||
} else {
|
} else {
|
||||||
return 0
|
log.Fatalf("Parse error: %s\n", line)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// 2nd pass, fill the sub-monkey fields
|
||||||
|
for _, m := range monkeys {
|
||||||
|
if !m.yelled {
|
||||||
|
m.m1 = monkeys[m.name1]
|
||||||
|
m.m2 = monkeys[m.name2]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Resolve and cache result. Faster but cannot be reused
|
||||||
|
func listenCached(m *Monkey) int {
|
||||||
|
if m.yelled {
|
||||||
|
return m.value
|
||||||
|
}
|
||||||
|
n1 := listenCached(m.m1)
|
||||||
|
n2 := listenCached(m.m2)
|
||||||
|
m.yelled = true
|
||||||
|
switch m.op {
|
||||||
|
case "+":
|
||||||
|
m.value = n1 + n2
|
||||||
|
case "-":
|
||||||
|
m.value = n1 - n2
|
||||||
|
case "*":
|
||||||
|
m.value = n1 * n2
|
||||||
|
case "/":
|
||||||
|
m.value = n1 / n2
|
||||||
|
}
|
||||||
|
return m.value
|
||||||
|
}
|
||||||
|
|
||||||
|
func listen(m *Monkey) int {
|
||||||
|
if m.yelled {
|
||||||
|
return m.value
|
||||||
|
}
|
||||||
|
n1 := listen(m.m1)
|
||||||
|
n2 := listen(m.m2)
|
||||||
|
switch m.op {
|
||||||
|
case "+":
|
||||||
|
return n1 + n2
|
||||||
|
case "-":
|
||||||
|
return n1 - n2
|
||||||
|
case "*":
|
||||||
|
return n1 * n2
|
||||||
|
case "/":
|
||||||
|
return n1 / n2
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
func listenFor(d, h *Monkey, value int) int {
|
||||||
|
h.value = value
|
||||||
|
return listen(d)
|
||||||
|
}
|
||||||
|
|
||||||
|
func humanZeroing(delta *Monkey) int {
|
||||||
|
human := monkeys["humn"]
|
||||||
|
human.yelled = true
|
||||||
|
h1 := 0
|
||||||
|
h2 := 1000
|
||||||
|
for {
|
||||||
|
d1 := listenFor(delta, human, h1)
|
||||||
|
d2 := listenFor(delta, human, h2)
|
||||||
|
d2d1 := d2 - d1
|
||||||
|
vprintf("%d, %d --> %d, %d (diff: %d)\n", h1, h2, d1, d2, d2d1)
|
||||||
|
if d1 == 0 {
|
||||||
|
return humanSmallest(delta, human, h1)
|
||||||
|
} // found
|
||||||
|
if d2 == 0 {
|
||||||
|
return humanSmallest(delta, human, h2)
|
||||||
|
} // found
|
||||||
|
if d2d1 == 0 {
|
||||||
|
log.Fatalf("d2 == d1\n")
|
||||||
|
}
|
||||||
|
// Linear regresion on h to minimize d
|
||||||
|
if absInt(d1) < absInt(d2) {
|
||||||
|
// h1 was closest to the goal: interpolate from it
|
||||||
|
h := h1 - (d1*(h2-h1))/d2d1
|
||||||
|
vprintf("==> h = %d (h1=%d + %d)\n", h, h1, -(d1*(h2-h1))/d2d1)
|
||||||
|
h2 = h
|
||||||
|
} else {
|
||||||
|
// h2 was closest to the goal: interpolate from it
|
||||||
|
h := h2 - (d2*(h1-h2))/(-d2d1)
|
||||||
|
vprintf("==> h = %d (h2=%d + %d)\n", h, h2, -(d2*(h1-h2))/(-d2d1))
|
||||||
|
h1 = h
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func humanSmallest(delta, human *Monkey, hval int) int {
|
||||||
|
h := hval
|
||||||
|
for listenFor(delta, human, h-1) == 0 {
|
||||||
|
h--
|
||||||
|
}
|
||||||
|
return h
|
||||||
|
}
|
||||||
|
|
||||||
|
func absInt(i int) int {
|
||||||
|
if i < 0 {
|
||||||
|
return -i
|
||||||
|
}
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
|
||||||
|
func vprintf(f string, vals ...interface{}) {
|
||||||
|
if !verbose {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Printf(f, vals...)
|
||||||
|
}
|
||||||
|
func vprint(txt string) {
|
||||||
|
if !verbose {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Println(txt)
|
||||||
|
}
|
||||||
|
@ -3,420 +3,400 @@ package main
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
h "git.bullercodeworks.com/brian/adventofcode/helpers"
|
h "git.bullercodeworks.com/brian/adventofcode/helpers"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
inp := h.StdinToStringSlice()
|
lines := h.StdinToStringSlice()
|
||||||
//part1(inp)
|
path := lines[len(lines)-1]
|
||||||
part2(inp)
|
m := lines[:len(lines)-2]
|
||||||
}
|
|
||||||
func part1(inp []string) {
|
p1 := NewFlatPuzzle(m)
|
||||||
board, inst := inp[:len(inp)-2], inp[len(inp)-1]
|
s1 := NewSolver(p1, path)
|
||||||
b := BuildBoard(board)
|
fmt.Println(s1.Password())
|
||||||
var count int
|
|
||||||
var turn byte
|
p2 := NewCubePuzzle(m)
|
||||||
fmt.Println(b)
|
s2 := NewSolver(p2, path)
|
||||||
fmt.Println(b.pos)
|
fmt.Println(s2.Password())
|
||||||
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())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func part2(inp []string) {
|
type Solver struct {
|
||||||
board, inst := inp[:len(inp)-2], inp[len(inp)-1]
|
puzzle Puzzle
|
||||||
b := BuildCubeBoard(board)
|
path string
|
||||||
var count int
|
coordinate Coordinate
|
||||||
var turn byte
|
facing Direction
|
||||||
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())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func getNextInstructions(inst string) (int, byte, string) {
|
func NewSolver(puzzle Puzzle, path string) *Solver {
|
||||||
for i := range inst {
|
solver := Solver{
|
||||||
if inst[i] == 'L' || inst[i] == 'R' {
|
puzzle: puzzle,
|
||||||
return h.Atoi(inst[:i]), inst[i], inst[i+1:]
|
path: path,
|
||||||
|
coordinate: puzzle.TopLeft(),
|
||||||
|
facing: RIGHT,
|
||||||
}
|
}
|
||||||
}
|
return &solver
|
||||||
return h.Atoi(inst), 0, ""
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 (
|
const (
|
||||||
dirR = iota
|
RIGHT Direction = iota
|
||||||
dirD
|
DOWN
|
||||||
dirL
|
LEFT
|
||||||
dirU
|
UP
|
||||||
dirErr
|
|
||||||
|
|
||||||
sideTop = iota
|
|
||||||
sideBottom
|
|
||||||
sideRight
|
|
||||||
sideLeft
|
|
||||||
sideFront
|
|
||||||
sideBack
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type CubeBoard struct {
|
var directions [4][2]int = [4][2]int{
|
||||||
Top *CubeBoardSide
|
{1, 0},
|
||||||
Bottom *CubeBoardSide
|
{0, 1},
|
||||||
Left *CubeBoardSide
|
{-1, 0},
|
||||||
Right *CubeBoardSide
|
{0, -1},
|
||||||
Front *CubeBoardSide
|
|
||||||
Back *CubeBoardSide
|
|
||||||
|
|
||||||
// Pos now holds:
|
|
||||||
// y, x, dir, side
|
|
||||||
pos []int
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// For the purposes of directions, we treat the board like this:
|
type Coordinate struct {
|
||||||
// [T]
|
X, Y int
|
||||||
// [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}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the current pos' side
|
type Tile bool
|
||||||
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 CubeBoardSide struct {
|
const (
|
||||||
spots [][]byte
|
OPEN Tile = true
|
||||||
}
|
WALL Tile = false
|
||||||
|
)
|
||||||
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
|
|
||||||
}
|
|
||||||
|
Loading…
Reference in New Issue
Block a user