package main import ( "fmt" "math" 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()) } 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()) } 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:] } } return h.Atoi(inst), 0, "" } const ( dirR = iota dirD dirL dirU dirErr sideTop = iota sideBottom sideRight sideLeft sideFront sideBack ) type CubeBoard struct { Top *CubeBoardSide Bottom *CubeBoardSide Left *CubeBoardSide Right *CubeBoardSide Front *CubeBoardSide Back *CubeBoardSide // Pos now holds: // y, x, dir, side pos []int } // 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} } // 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 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 }