2018 day 13 complete!
This commit is contained in:
parent
c94dfb5958
commit
52629a7b9c
@ -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()
|
||||
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
|
||||
func part1() {
|
||||
var crashed bool
|
||||
for !crashed {
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
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
|
||||
func part2() {
|
||||
for len(cartMap) > 1 {
|
||||
sort.Sort(ByPos(carts))
|
||||
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
|
||||
if *c != 0 {
|
||||
cartMap[*c].tick()
|
||||
}
|
||||
}
|
||||
}
|
||||
fmt.Println("")
|
||||
for pos := range cartMap {
|
||||
fmt.Printf("🔵: %0.0f,%0.0f\n", real(pos), imag(pos))
|
||||
}
|
||||
}
|
||||
|
||||
type Pos struct {
|
||||
x, y int
|
||||
dir int
|
||||
nextTurn int
|
||||
type Cart struct {
|
||||
pos complex64
|
||||
dir complex64
|
||||
nextTurn complex64
|
||||
}
|
||||
|
||||
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)
|
||||
// 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
|
||||
}
|
||||
} 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
|
||||
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
|
||||
}
|
||||
// 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]))
|
||||
}
|
||||
|
||||
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)
|
||||
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 '^':
|
||||
p.dir = DIR_N
|
||||
carts = append(carts, p)
|
||||
cartMap[pos] = &Cart{pos, DIR_N, TURN_L}
|
||||
case '>':
|
||||
p.dir = DIR_E
|
||||
carts = append(carts, p)
|
||||
cartMap[pos] = &Cart{pos, DIR_E, TURN_L}
|
||||
case 'v':
|
||||
cartMap[pos] = &Cart{pos, DIR_S, TURN_L}
|
||||
case '<':
|
||||
p.dir = DIR_W
|
||||
carts = append(carts, p)
|
||||
cartMap[pos] = &Cart{pos, DIR_W, TURN_L}
|
||||
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 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
|
||||
}
|
||||
|
251
2018/day13/problem
Normal file
251
2018/day13/problem
Normal 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
|
Loading…
Reference in New Issue
Block a user