From 52629a7b9c870aeba2d66504a8f4bccb5c7547ab Mon Sep 17 00:00:00 2001 From: Brian Buller Date: Thu, 13 Dec 2018 12:13:56 -0600 Subject: [PATCH] 2018 day 13 complete! --- 2018/day13/day13.go | 370 ++++++++++++-------------------------------- 2018/day13/problem | 251 ++++++++++++++++++++++++++++++ 2 files changed, 353 insertions(+), 268 deletions(-) create mode 100644 2018/day13/problem diff --git a/2018/day13/day13.go b/2018/day13/day13.go index 022b448..ad78a9f 100644 --- a/2018/day13/day13.go +++ b/2018/day13/day13.go @@ -5,311 +5,145 @@ import ( "fmt" "os" "sort" - "time" ) const ( - DIR_N = iota - DIR_E - DIR_S - DIR_W - DIR_ERR + DIR_N = -1i + DIR_E = 1 + DIR_S = 1i + DIR_W = -1 - TURN_L = iota - TURN_S - TURN_R - TURN_ERR - - ClearScreen = "\033[H\033[2J" + TURN_L = -1i + TURN_S = 1 + TURN_R = 1i ) -var course [][]Track -var carts []*Pos +var width int +var input []byte +var carts []*complex64 +var cartMap map[complex64]*Cart func main() { - inp := StdinToStringSlice() - part1(inp) + stdinToByteSlice() + setupCarts() + //part1() + part2() } -func part1(inp []string) { - course, carts = initScrubTrack(inp) - var done bool - for i := 0; ; i++ { - printState() - tick() +func part1() { + var crashed bool + for !crashed { + sort.Sort(ByPos(carts)) for _, c := range carts { - if c.dir == DIR_ERR { - fmt.Println("= Part 1 =") - 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 - } + if !cartMap[*c].tick() { + return } } } } -func getTrackAt(x, y int) Track { - if x < 0 || y < 0 || y >= len(course) || x >= len(course[0]) { - return Track(' ') - } - return course[y][x] -} - -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 +func part2() { + for len(cartMap) > 1 { + sort.Sort(ByPos(carts)) + for _, c := range carts { + if *c != 0 { + cartMap[*c].tick() } } } - return Pos{x: -1, y: -1}, false + for pos := range cartMap { + fmt.Printf("🔵: %0.0f,%0.0f\n", real(pos), imag(pos)) + } } -func printState() { - fmt.Print(ClearScreen) - for y, v := range course { - for x, t := range v { - var foundCarts []*Pos - for _, c := range carts { - if c.x == x && c.y == y { - foundCarts = append(foundCarts, c) - } - } - if len(foundCarts) == 0 { - fmt.Print(string(t)) - } else if len(foundCarts) == 1 { - fmt.Print(string(foundCarts[0].getCartByte())) - } else { - fmt.Print("X") - for _, c := range foundCarts { - c.dir = DIR_ERR - } - } +type Cart struct { + pos complex64 + dir complex64 + nextTurn complex64 +} + +// Tick the cart, return if it's still alive +func (c *Cart) tick() bool { + // Remove this cart from it's old position + // and figure out it's new one + delete(cartMap, c.pos) + c.pos += c.dir + if cart, ok := cartMap[c.pos]; ok { + fmt.Printf("❌: %0.0f,%0.0f\n", real(c.pos), imag(c.pos)) + // Remove the cart we crashed into from the map + delete(cartMap, c.pos) + // And clear the cart's pos, we can ignore them now + 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 { - x, y int - dir int - nextTurn int +// We have to sort the carts on each tick by y,x position +// y is the imaginary part, x is the real part +type ByPos []*complex64 + +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 - -func (b ByPos) Len() int { return len(b) } -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) == '+' +// getByte pulls a byte from the given position in the input +func getByte(pos complex64) byte { + return input[int(real(pos))+int(imag(pos))*width] } /** * Initialization Functions */ -// Scrub the track, return the track, sans carts, and all carts -func initScrubTrack(inp []string) ([][]Track, []*Pos) { - var carts []*Pos - var course [][]Track - for y, v := range inp { - var row []Track - for x, b := range v { - p := &Pos{x: x, y: y} - switch b { - case 'v': - p.dir = DIR_S - carts = append(carts, p) - case '^': - p.dir = DIR_N - carts = append(carts, p) - case '>': - p.dir = DIR_E - carts = append(carts, p) - case '<': - p.dir = DIR_W - carts = append(carts, p) - } - row = append(row, initGetTrack(inp, *p)) +func setupCarts() { + cartMap = make(map[complex64]*Cart) + for i := 0; i < len(input); i++ { + pos := complex(float32(i%width), float32(i/width)) + switch getByte(pos) { + case '^': + cartMap[pos] = &Cart{pos, DIR_N, TURN_L} + case '>': + cartMap[pos] = &Cart{pos, DIR_E, TURN_L} + case 'v': + cartMap[pos] = &Cart{pos, DIR_S, TURN_L} + case '<': + cartMap[pos] = &Cart{pos, DIR_W, TURN_L} + default: + continue // Not a cart } - course = append(course, row) + carts = append(carts, &cartMap[pos].pos) } - return course, carts } -// Get a track position -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 +func stdinToByteSlice() { scanner := bufio.NewScanner(os.Stdin) for scanner.Scan() { - input = append(input, scanner.Text()) + data := scanner.Bytes() + if width == 0 { + width = len(data) + } + input = append(input, data...) } - return input } diff --git a/2018/day13/problem b/2018/day13/problem new file mode 100644 index 0000000..d40db9e --- /dev/null +++ b/2018/day13/problem @@ -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