316 lines
5.8 KiB
Go
316 lines
5.8 KiB
Go
package main
|
|
|
|
import (
|
|
"bufio"
|
|
"fmt"
|
|
"os"
|
|
"sort"
|
|
"time"
|
|
)
|
|
|
|
const (
|
|
DIR_N = iota
|
|
DIR_E
|
|
DIR_S
|
|
DIR_W
|
|
DIR_ERR
|
|
|
|
TURN_L = iota
|
|
TURN_S
|
|
TURN_R
|
|
TURN_ERR
|
|
|
|
ClearScreen = "\033[H\033[2J"
|
|
)
|
|
|
|
var course [][]Track
|
|
var carts []*Pos
|
|
|
|
func main() {
|
|
inp := StdinToStringSlice()
|
|
part1(inp)
|
|
}
|
|
|
|
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
|
|
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 {
|
|
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
|
|
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
|
|
}
|
|
}
|
|
}
|
|
fmt.Println("")
|
|
}
|
|
}
|
|
|
|
type Pos struct {
|
|
x, y int
|
|
dir int
|
|
nextTurn int
|
|
}
|
|
|
|
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) == '+'
|
|
}
|
|
|
|
/**
|
|
* 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))
|
|
}
|
|
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
|
|
scanner := bufio.NewScanner(os.Stdin)
|
|
for scanner.Scan() {
|
|
input = append(input, scanner.Text())
|
|
}
|
|
return input
|
|
}
|