2018 day 13 complete!

This commit is contained in:
Brian Buller 2018-12-13 12:13:56 -06:00
parent c94dfb5958
commit 52629a7b9c
2 changed files with 353 additions and 268 deletions

View File

@ -5,311 +5,145 @@ import (
"fmt" "fmt"
"os" "os"
"sort" "sort"
"time"
) )
const ( const (
DIR_N = iota DIR_N = -1i
DIR_E DIR_E = 1
DIR_S DIR_S = 1i
DIR_W DIR_W = -1
DIR_ERR
TURN_L = iota TURN_L = -1i
TURN_S TURN_S = 1
TURN_R TURN_R = 1i
TURN_ERR
ClearScreen = "\033[H\033[2J"
) )
var course [][]Track var width int
var carts []*Pos var input []byte
var carts []*complex64
var cartMap map[complex64]*Cart
func main() { func main() {
inp := StdinToStringSlice() stdinToByteSlice()
part1(inp) setupCarts()
//part1()
part2()
} }
func part1(inp []string) { func part1() {
course, carts = initScrubTrack(inp) var crashed bool
var done bool for !crashed {
for i := 0; ; i++ { sort.Sort(ByPos(carts))
printState()
tick()
for _, c := range carts { for _, c := range carts {
if c.dir == DIR_ERR { if !cartMap[*c].tick() {
fmt.Println("= Part 1 =") return
fmt.Println(c.string())
fmt.Println(i, "ticks")
done = true
break
}
}
if done {
break
}
if i%1000000 == 0 {
fmt.Print(".")
}
time.Sleep(time.Millisecond * 250)
}
}
func tick() {
// sort the carts list by y/x
sort.Sort(ByPos(carts))
for _, c := range carts {
switch c.dir {
case DIR_N:
t := getTrackAt(c.x, c.y-1)
if t.goesDown() {
c.y--
if byte(t) == '+' {
c.chooseTurn()
} else if byte(t) == '\\' {
c.dir = DIR_W
} else if byte(t) == '/' {
c.dir = DIR_E
}
}
case DIR_E:
t := getTrackAt(c.x+1, c.y)
if t.goesLeft() {
c.x++
if byte(t) == '+' {
c.chooseTurn()
} else if byte(t) == '/' {
c.dir = DIR_N
} else if byte(t) == '\\' {
c.dir = DIR_S
}
}
case DIR_S:
t := getTrackAt(c.x, c.y+1)
if t.goesUp() {
c.y++
if byte(t) == '+' {
c.chooseTurn()
} else if byte(t) == '\\' {
c.dir = DIR_E
} else if byte(t) == '/' {
c.dir = DIR_W
}
}
case DIR_W:
t := getTrackAt(c.x-1, c.y)
if t.goesRight() {
c.x--
if byte(t) == '+' {
c.chooseTurn()
} else if byte(t) == '/' {
c.dir = DIR_S
} else if byte(t) == '\\' {
c.dir = DIR_N
}
} }
} }
} }
} }
func getTrackAt(x, y int) Track { func part2() {
if x < 0 || y < 0 || y >= len(course) || x >= len(course[0]) { for len(cartMap) > 1 {
return Track(' ') sort.Sort(ByPos(carts))
} for _, c := range carts {
return course[y][x] if *c != 0 {
} cartMap[*c].tick()
func findCrash() (Pos, bool) {
for _, c1 := range carts {
for _, c2 := range carts {
if c1 == c2 {
continue
}
if c1.x == c2.x && c1.y == c2.y {
return Pos{x: c1.x, y: c1.y}, true
} }
} }
} }
return Pos{x: -1, y: -1}, false for pos := range cartMap {
fmt.Printf("🔵: %0.0f,%0.0f\n", real(pos), imag(pos))
}
} }
func printState() { type Cart struct {
fmt.Print(ClearScreen) pos complex64
for y, v := range course { dir complex64
for x, t := range v { nextTurn complex64
var foundCarts []*Pos }
for _, c := range carts {
if c.x == x && c.y == y { // Tick the cart, return if it's still alive
foundCarts = append(foundCarts, c) func (c *Cart) tick() bool {
} // Remove this cart from it's old position
} // and figure out it's new one
if len(foundCarts) == 0 { delete(cartMap, c.pos)
fmt.Print(string(t)) c.pos += c.dir
} else if len(foundCarts) == 1 { if cart, ok := cartMap[c.pos]; ok {
fmt.Print(string(foundCarts[0].getCartByte())) fmt.Printf("❌: %0.0f,%0.0f\n", real(c.pos), imag(c.pos))
} else { // Remove the cart we crashed into from the map
fmt.Print("X") delete(cartMap, c.pos)
for _, c := range foundCarts { // And clear the cart's pos, we can ignore them now
c.dir = DIR_ERR cart.pos, c.pos = 0, 0
} return false
} }
// No crash, figure out the new orientation of the cart
cartMap[c.pos] = c
if getByte(c.pos) == '+' {
c.dir *= c.nextTurn
switch c.nextTurn {
case TURN_L:
c.nextTurn = TURN_S
case TURN_S:
c.nextTurn = TURN_R
case TURN_R:
c.nextTurn = TURN_L
} }
fmt.Println("") } else if getByte(c.pos) == '/' {
// Turn counter-clockwise
c.dir = complex(-imag(c.dir), -real(c.dir))
} else if getByte(c.pos) == '\\' {
// Turn clockwise
c.dir = complex(imag(c.dir), real(c.dir))
} }
return true
} }
type Pos struct { // We have to sort the carts on each tick by y,x position
x, y int // y is the imaginary part, x is the real part
dir int type ByPos []*complex64
nextTurn int
func (c ByPos) Len() int { return len(c) }
func (c ByPos) Swap(i, j int) { c[i], c[j] = c[j], c[i] }
func (c ByPos) Less(i, j int) bool {
return imag(*c[i]) < imag(*c[j]) ||
(imag(*c[i]) == imag(*c[j]) && real(*c[i]) < real(*c[j]))
} }
type ByPos []*Pos // getByte pulls a byte from the given position in the input
func getByte(pos complex64) byte {
func (b ByPos) Len() int { return len(b) } return input[int(real(pos))+int(imag(pos))*width]
func (b ByPos) Swap(i, j int) { b[i], b[j] = b[j], b[i] }
func (b ByPos) Less(i, j int) bool {
return b[i].y < b[j].y || (b[i].y == b[j].y && b[i].x < b[j].x)
}
// Choose a turn based on nextTurn and execute it
func (p *Pos) chooseTurn() {
if p.nextTurn == TURN_S {
p.nextTurn = TURN_R
} else if p.nextTurn == TURN_R {
p.turnRight()
p.nextTurn = TURN_L
} else { // Default to turn left
p.turnLeft()
p.nextTurn = TURN_S
}
}
func (p *Pos) turnLeft() {
p.dir--
if p.dir < DIR_N {
p.dir = DIR_W
}
}
func (p *Pos) turnRight() {
p.dir++
if p.dir > DIR_W {
p.dir = DIR_N
}
}
func (p *Pos) string() string {
return fmt.Sprintf("(%2d,%2d: %s)", p.x, p.y, string(p.getCartByte()))
}
func (p *Pos) getCartByte() byte {
switch p.dir {
case DIR_N:
return '^'
case DIR_E:
return '>'
case DIR_S:
return 'v'
case DIR_W:
return '<'
}
return ' '
}
type Track byte
func (t Track) isEmpty() bool {
return byte(t) == ' '
}
func (t Track) goesUp() bool {
return byte(t) == '|' || byte(t) == '/' || byte(t) == '\\' || byte(t) == '+'
}
func (t Track) goesDown() bool {
return byte(t) == '|' || byte(t) == '/' || byte(t) == '\\' || byte(t) == '+'
}
func (t Track) goesRight() bool {
return byte(t) == '-' || byte(t) == '\\' || byte(t) == '/' || byte(t) == '+'
}
func (t Track) goesLeft() bool {
return byte(t) == '-' || byte(t) == '\\' || byte(t) == '/' || byte(t) == '+'
} }
/** /**
* Initialization Functions * Initialization Functions
*/ */
// Scrub the track, return the track, sans carts, and all carts func setupCarts() {
func initScrubTrack(inp []string) ([][]Track, []*Pos) { cartMap = make(map[complex64]*Cart)
var carts []*Pos for i := 0; i < len(input); i++ {
var course [][]Track pos := complex(float32(i%width), float32(i/width))
for y, v := range inp { switch getByte(pos) {
var row []Track case '^':
for x, b := range v { cartMap[pos] = &Cart{pos, DIR_N, TURN_L}
p := &Pos{x: x, y: y} case '>':
switch b { cartMap[pos] = &Cart{pos, DIR_E, TURN_L}
case 'v': case 'v':
p.dir = DIR_S cartMap[pos] = &Cart{pos, DIR_S, TURN_L}
carts = append(carts, p) case '<':
case '^': cartMap[pos] = &Cart{pos, DIR_W, TURN_L}
p.dir = DIR_N default:
carts = append(carts, p) continue // Not a cart
case '>':
p.dir = DIR_E
carts = append(carts, p)
case '<':
p.dir = DIR_W
carts = append(carts, p)
}
row = append(row, initGetTrack(inp, *p))
} }
course = append(course, row) carts = append(carts, &cartMap[pos].pos)
} }
return course, carts
} }
// Get a track position func stdinToByteSlice() {
func initGetTrack(inp []string, p Pos) Track {
if p.y < 0 || p.x < 0 || p.x > len(inp[0]) || p.y > len(inp) {
return Track(' ')
}
var bTop, bBot, bLft, bRgt Track
if initIsCart(inp, p) {
bTop = initGetTrack(inp, Pos{x: p.x, y: p.y - 1})
bBot = initGetTrack(inp, Pos{x: p.x, y: p.y + 1})
bLft = initGetTrack(inp, Pos{x: p.x - 1, y: p.y})
bRgt = initGetTrack(inp, Pos{x: p.x + 1, y: p.y})
if bTop.goesDown() && bLft.goesRight() && bRgt.goesLeft() && bBot.goesUp() {
return Track('+')
}
if (bTop.isEmpty() || bBot.isEmpty()) && bRgt.goesLeft() && bLft.goesRight() {
return Track('-')
}
if (bLft.isEmpty() || bRgt.isEmpty()) && bTop.goesDown() && bBot.goesUp() {
return Track('|')
}
}
return Track(inp[p.y][p.x])
}
// Return if this position is a cart
func initIsCart(inp []string, p Pos) bool {
switch inp[p.y][p.x] {
case 'v', '^', '<', '>':
return true
}
return false
}
func StdinToStringSlice() []string {
var input []string
scanner := bufio.NewScanner(os.Stdin) scanner := bufio.NewScanner(os.Stdin)
for scanner.Scan() { for scanner.Scan() {
input = append(input, scanner.Text()) data := scanner.Bytes()
if width == 0 {
width = len(data)
}
input = append(input, data...)
} }
return input
} }

251
2018/day13/problem Normal file
View File

@ -0,0 +1,251 @@
Advent of Code
--- Day 13: Mine Cart Madness ---
A crop of this size requires significant logistics to transport produce, soil, fertilizer, and so on. The Elves are very busy pushing
things around in carts on some kind of rudimentary system of tracks they've come up with.
Seeing as how cart-and-track systems don't appear in recorded history for another 1000 years, the Elves seem to be making this up as
they go along. They haven't even figured out how to avoid collisions yet.
You map out the tracks (your puzzle input) and see where you can help.
Tracks consist of straight paths (| and -), curves (/ and \), and intersections (+). Curves connect exactly two perpendicular pieces
of track; for example, this is a closed loop:
/----\
| |
| |
\----/
Intersections occur when two perpendicular paths cross. At an intersection, a cart is capable of turning left, turning right, or
continuing straight. Here are two loops connected by two intersections:
/-----\
| |
| /--+--\
| | | |
\--+--/ |
| |
\-----/
Several carts are also on the tracks. Carts always face either up (^), down (v), left (<), or right (>). (On your initial map, the
track under each cart is a straight path matching the direction the cart is facing.)
Each time a cart has the option to turn (by arriving at any intersection), it turns left the first time, goes straight the second
time, turns right the third time, and then repeats those directions starting again with left the fourth time, straight the fifth
time, and so on. This process is independent of the particular intersection at which the cart has arrived - that is, the cart has no
per-intersection memory.
Carts all move at the same speed; they take turns moving a single step at a time. They do this based on their current location: carts
on the top row move first (acting from left to right), then carts on the second row move (again from left to right), then carts on
the third row, and so on. Once each cart has moved one step, the process repeats; each of these loops is called a tick.
For example, suppose there are two carts on a straight track:
| | | | |
v | | | |
| v v | |
| | | v X
| | ^ ^ |
^ ^ | | |
| | | | |
First, the top cart moves. It is facing down (v), so it moves down one square. Second, the bottom cart moves. It is facing up (^), so
it moves up one square. Because all carts have moved, the first tick ends. Then, the process repeats, starting with the first cart.
The first cart moves down, then the second cart moves up - right into the first cart, colliding with it! (The location of the crash
is marked with an X.) This ends the second and last tick.
Here is a longer example:
/->-\
| | /----\
| /-+--+-\ |
| | | | v |
\-+-/ \-+--/
\------/
/-->\
| | /----\
| /-+--+-\ |
| | | | | |
\-+-/ \->--/
\------/
/---v
| | /----\
| /-+--+-\ |
| | | | | |
\-+-/ \-+>-/
\------/
/---\
| v /----\
| /-+--+-\ |
| | | | | |
\-+-/ \-+->/
\------/
/---\
| | /----\
| /->--+-\ |
| | | | | |
\-+-/ \-+--^
\------/
/---\
| | /----\
| /-+>-+-\ |
| | | | | ^
\-+-/ \-+--/
\------/
/---\
| | /----\
| /-+->+-\ ^
| | | | | |
\-+-/ \-+--/
\------/
/---\
| | /----<
| /-+-->-\ |
| | | | | |
\-+-/ \-+--/
\------/
/---\
| | /---<\
| /-+--+>\ |
| | | | | |
\-+-/ \-+--/
\------/
/---\
| | /--<-\
| /-+--+-v |
| | | | | |
\-+-/ \-+--/
\------/
/---\
| | /-<--\
| /-+--+-\ |
| | | | v |
\-+-/ \-+--/
\------/
/---\
| | /<---\
| /-+--+-\ |
| | | | | |
\-+-/ \-<--/
\------/
/---\
| | v----\
| /-+--+-\ |
| | | | | |
\-+-/ \<+--/
\------/
/---\
| | /----\
| /-+--v-\ |
| | | | | |
\-+-/ ^-+--/
\------/
/---\
| | /----\
| /-+--+-\ |
| | | X | |
\-+-/ \-+--/
\------/
After following their respective paths for a while, the carts eventually crash. To help prevent crashes, you'd like to know the
location of the first crash. Locations are given in X,Y coordinates, where the furthest left column is X=0 and the furthest top row
is Y=0:
111
0123456789012
0/---\
1| | /----\
2| /-+--+-\ |
3| | | X | |
4\-+-/ \-+--/
5 \------/
In this example, the location of the first crash is 7,3.
Your puzzle answer was 83,106.
--- Part Two ---
There isn't much you can do to prevent crashes in this ridiculous system. However, by predicting the crashes, the Elves know where to
be in advance and instantly remove the two crashing carts the moment any crash occurs.
They can proceed like this for a while, but eventually, they're going to run out of carts. It could be useful to figure out where the
last cart that hasn't crashed will end up.
For example:
/>-<\
| |
| /<+-\
| | | v
\>+</ |
| ^
\<->/
/---\
| |
| v-+-\
| | | |
\-+-/ |
| |
^---^
/---\
| |
| /-+-\
| v | |
\-+-/ |
^ ^
\---/
/---\
| |
| /-+-\
| | | |
\-+-/ ^
| |
\---/
After four very expensive crashes, a tick ends with only one cart remaining; its final location is 6,4.
What is the location of the last cart at the end of the first tick where it is the only cart left?
Your puzzle answer was 132,26.
Both parts of this puzzle are complete! They provide two gold stars: **
References
Visible links
. https://adventofcode.com/
. https://adventofcode.com/2018/about
. https://adventofcode.com/2018/events
. https://adventofcode.com/2018/settings
. https://adventofcode.com/2018/auth/logout
. Advent of Code Supporter
https://adventofcode.com/2018/support
. https://adventofcode.com/2018
. https://adventofcode.com/2018
. https://adventofcode.com/2018/support
. https://adventofcode.com/2018/sponsors
. https://adventofcode.com/2018/leaderboard
. https://adventofcode.com/2018/stats
. https://adventofcode.com/2018/sponsors
. https://adventofcode.com/2018
. https://adventofcode.com/2018/day/13/input