Skip to content

Day 14. Restroom Redoubt

Historians need a bathroom break and we need to help them to avoid the new Easter Bunny Headquarters bathroom guard robots. Robots move in straight lines and have constant speed. They also have teleports to not crash into the walls.

Part One

In the first part, we need to calculate the safety factor by predicting all the robots position after 100 seconds.

Robot movement pattern is simple and we can skip step-by-step simulation and calculate position on arbitrary time step directly. When robot reaches the wall, it teleports to the other side, so we can use modulo operation for our calculations:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
type Robot struct {
    Row, Col   int
    VelR, VelC int
}

func (r Robot) Simulate(modRow int, modCol int, ticks int) Robot {
    if ticks <= 0 {
        panic(fmt.Sprintf("Invalid number of ticks: %d", ticks))
    }

    rowDelta := calculateDelta(r.VelR, ticks, modRow)
    newRow := modAdd(r.Row, rowDelta, modRow)

    colDelta := calculateDelta(r.VelC, ticks, modCol)
    newCol := modAdd(r.Col, colDelta, modCol)

    return Robot{Row: newRow, Col: newCol, VelR: r.VelR, VelC: r.VelC}
}

// In go `-7 % 5` is -2, so we use modulo arythmetic properties
// to get the positive result
func calculateDelta(velocity int, ticks int, mod int) int {
    delta := velocity * ticks % mod
    if delta < 0 {
        delta += mod
    }
    return delta
}

func modAdd(a int, b int, mod int) int {
    res := (a + b) % mod
    if res < 0 {
        res += mod
    }
    return res
}

Main function and safety factor calculation are also straightforward:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
const (
    testRows  = 7
    testCols  = 11
    inputRows = 103
    inputCols = 101
)

type ParsedInput = []Robot

const partOneSteps = 100

func PartOne(inp ParsedInput) int {
    defer Track(time.Now(), "PartOne")
    rows, cols := inputRows, inputCols

    robots := slices.Clone(inp)
    simulated := make([]Robot, len(robots))
    for i, robot := range robots {
        simulated[i] = robot.Simulate(rows, cols, partOneSteps)
    }

    sf := safetyFactor(rows, cols, simulated)

    return sf
}

func getQuadrant(midRow, midCol int, r Robot) int {
    if r.Row == midRow || r.Col == midCol {
        panic(fmt.Sprintf("middle row or column: %s", r))
    }
    if r.Row < midRow {
        if r.Col < midCol {
            return 0
        }
        return 1
    }
    if r.Col < midCol {
        return 2
    }
    return 3
}

func safetyFactor(rows, cols int, robots []Robot) int {
    // Odd number by task rules, so to find the middle we can just divide by 2
    midRow, midCol := rows/2, cols/2
    quadrants := make([]int, 4)
    for _, r := range robots {
        if r.Row == midRow || r.Col == midCol {
            continue
        }
        q := getQuadrant(midRow, midCol, r)
        quadrants[q] += 1
    }
    sf := 1
    for _, q := range quadrants {
        sf *= q
    }

    return sf
}

Part Two

In the second part someone mentions that these robots are similar to the ones they use in the North Pole. Those models have an easter egg feature: very rarely they may arrange themseves in a Christmas tree shape. We need to find how long would it take.

The pattern is unknown and time to wait may be very long. We can't look at all the possible time steps, so we need to find some flag to stop the simulation and show us current state. My first guess was that christmas tree needs to have a vertical trunk. So I've decided to look for the vertical sequence of 10 robots in the same column. And to my surprise, it worked on the first try:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
func (r Robot) ToKey() int {
    return toKey(r.Row, r.Col)
}

func toKey(row, col int) int {
    return row*100000 + col
}


const (
    trunkSeqSize = 10
    printTree    = true
)

func PartTwo(inp ParsedInput) int {
    defer Track(time.Now(), "PartTwo")
    rows, cols := inputRows, inputCols

    robots := slices.Clone(inp)
    for sec := 1; sec <= 100000; sec++ {
        robotsByCol := make([][]int, cols)
        for i, robot := range robots {
            newRobot := robot.Simulate(rows, cols, 1)
            robotsByCol[newRobot.Col] = append(robotsByCol[newRobot.Col], newRobot.Row)
            robots[i] = newRobot
        }

        for c, column := range robotsByCol {
            slices.Sort(column)
            if !findSequence(column, trunkSeqSize) {
                continue
            }
            if printTree {
                fmt.Println(StringForTree(robots, rows, cols))
                fmt.Printf("\n%d sec, found %d in column %d:\n%v\n", sec, trunkSeqSize, c, column)
            }
            return sec
        }

    }

    return 0
}

func findSequence(toCheck []int, size int) bool {
    if len(toCheck) < size {
        return false
    }
    foundSize := 1
    previous := toCheck[0]

    for i := 1; i < len(toCheck); i++ {
        if i+size >= len(toCheck) {
            return false
        }
        num := toCheck[i]
        if num == previous {
            continue
        }
        if num == previous+1 {
            foundSize++
        } else {
            foundSize = 1
        }
        previous = num
        if foundSize == size {
            return true
        }
    }

    return false
}

func StringForTree(robots []Robot, rows, cols int) string {
    var sb strings.Builder

    counts := make(map[int]int8)
    for _, robot := range robots {
        key := robot.ToKey()
        counts[key] += 1
    }

    for r := range rows {
        for c := range cols {
            if val, ok := counts[toKey(r, c)]; ok && val > 0 {
                if val > 9 {
                    panic(fmt.Sprintf("Too many robots at %d,%d: %d", r, c, val))
                }
                sb.WriteRune('#')
                continue
            }
            sb.WriteRune('.')
        }
        sb.WriteRune('\n')
    }

    return sb.String()
}

Tags

  • visualization
  • modulo operations