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++ {
printState()
tick()
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)) sort.Sort(ByPos(carts))
for _, c := range carts { for _, c := range carts {
switch c.dir { if !cartMap[*c].tick() {
case DIR_N: return
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))
}
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
}
}
}
return Pos{x: -1, y: -1}, false
}
func printState() {
fmt.Print(ClearScreen)
for y, v := range course {
for x, t := range v {
var foundCarts []*Pos
for _, c := range carts { for _, c := range carts {
if c.x == x && c.y == y { if *c != 0 {
foundCarts = append(foundCarts, c) cartMap[*c].tick()
}
}
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
} }
} }
} }
fmt.Println("") for pos := range cartMap {
fmt.Printf("🔵: %0.0f,%0.0f\n", real(pos), imag(pos))
} }
} }
type Pos struct { type Cart struct {
x, y int pos complex64
dir int dir complex64
nextTurn int nextTurn complex64
} }
type ByPos []*Pos // Tick the cart, return if it's still alive
func (c *Cart) tick() bool {
func (b ByPos) Len() int { return len(b) } // Remove this cart from it's old position
func (b ByPos) Swap(i, j int) { b[i], b[j] = b[j], b[i] } // and figure out it's new one
func (b ByPos) Less(i, j int) bool { delete(cartMap, c.pos)
return b[i].y < b[j].y || (b[i].y == b[j].y && b[i].x < b[j].x) 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
}
} 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
} }
// Choose a turn based on nextTurn and execute it // We have to sort the carts on each tick by y,x position
func (p *Pos) chooseTurn() { // y is the imaginary part, x is the real part
if p.nextTurn == TURN_S { type ByPos []*complex64
p.nextTurn = TURN_R
} else if p.nextTurn == TURN_R { func (c ByPos) Len() int { return len(c) }
p.turnRight() func (c ByPos) Swap(i, j int) { c[i], c[j] = c[j], c[i] }
p.nextTurn = TURN_L func (c ByPos) Less(i, j int) bool {
} else { // Default to turn left return imag(*c[i]) < imag(*c[j]) ||
p.turnLeft() (imag(*c[i]) == imag(*c[j]) && real(*c[i]) < real(*c[j]))
p.nextTurn = TURN_S
}
} }
func (p *Pos) turnLeft() { // getByte pulls a byte from the given position in the input
p.dir-- func getByte(pos complex64) byte {
if p.dir < DIR_N { return input[int(real(pos))+int(imag(pos))*width]
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
for x, b := range v {
p := &Pos{x: x, y: y}
switch b {
case 'v':
p.dir = DIR_S
carts = append(carts, p)
case '^': case '^':
p.dir = DIR_N cartMap[pos] = &Cart{pos, DIR_N, TURN_L}
carts = append(carts, p)
case '>': case '>':
p.dir = DIR_E cartMap[pos] = &Cart{pos, DIR_E, TURN_L}
carts = append(carts, p) case 'v':
cartMap[pos] = &Cart{pos, DIR_S, TURN_L}
case '<': case '<':
p.dir = DIR_W cartMap[pos] = &Cart{pos, DIR_W, TURN_L}
carts = append(carts, p) default:
continue // Not a cart
} }
row = append(row, initGetTrack(inp, *p)) carts = append(carts, &cartMap[pos].pos)
} }
course = append(course, row)
}
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