Distance covered by a bouncing ball. Say you have a rectangle. A ball, with a given velocity (in terms of angle and magnitude), is placed anywhere in the rectangle and starts bouncing (without friction).

*

*The rectangle is of width and length x and y.

*The velocity has a magnitude of v and an angle θ.

Here's the question: How much distance has the ball covered after n bounces?
I know it has to do something with series, but I'm not proficient in the topic yet. My approach to this is 'unravelling' the box into the 2d plane, therefore the path the ball takes becomes a straight line, and the intersections are the bounces. But I got no further. I suppose the ball could start at the edge or corner to make it easier, but I run into the same problems.
Feel free to assume numbers since this is a hypothetical question. I did ask this before but I'm clarifying it here. Either way, tell me if I missed anything. Thank you.
 A: Let's say you have an axis-aligned box at $(-r, -r) - (w+r, h+h)$, and a frictionless ball of radius $r$.  This means the center of the ball, $(x, y)$, is constricted to $0 \le x \le w$, $0 \le y \le h$.
Let's say the ball is initially placed in $(x_0, y_0)$, and is moving in direction $\theta$ (with $\theta = 0$ towards positive $x$ axis, $\theta = 90°$ towards positive $y$ axis, and so on).  As an unit direction vector, the direction $(\Delta_x, \Delta_y)$ is then $(\cos \theta, \sin \theta)$:
$$\left\lbrace ~ \begin{aligned}
\Delta_x &= \cos\theta \\
\Delta_y &= \sin\theta \\
\end{aligned} \right . \tag{1} \label{1}$$
Since the ball travels $\Delta_x$ along the $x$ axis and $\Delta_y$ along the $y$ axis when the ball travels one unit of distance, the ball travels
$$L_w = \frac{w}{\lvert\Delta_x\rvert} \tag{2a}\label{2a}$$
between bounces from the vertical walls at $x=-r$ and $x=w+r$; and
$$L_h = \frac{h}{\lvert\Delta_y\rvert} \tag{2b}\label{2b}$$
between bounces from the horizontal walls at $y=-r$ and $y=h+r$.
The distance the ball travels before the very first bounce from a vertical wall, $\ell_w$, depends obviously on the direction, since it can occur from the $x=-r$ or $x=w+r$ walls:
$$\ell_w = \begin{cases}
\frac{w - x_0}{\Delta_x}, & \Delta_x \gt 0 \\
\frac{x_0}{-\Delta_x}, & \Delta_x \lt 0 \\
\infty, & \cos\theta = 0 \\
\end{cases} \tag{3a}\label{3a}$$
and similarly for the horizontal walls at $y=0$ or $y=w$,
$$\ell_h = \begin{cases}
\frac{h - y_0}{\Delta_y}, & \Delta_y \gt 0 \\
\frac{y_0}{-\Delta_y}, & \Delta_y \lt 0 \\
\infty, & \sin\theta = 0 \\
\end{cases} \tag{3b}\label{3b}$$
(In $N$ dimensions, we have $N$ pairs of initial and interval distances, otherwise the situation stays exactly the same.)
For simplicity, let's consider bounces that never occur –– because the ball is moving exactly parallel to the $x$ axis or to the $y$ axis –– to happen at infinite distance.
Next, we construct two sequences of distances the ball has traveled for a bounce to occur.
One sequence is $\ell_w$, $\ell_w + L_w$, $\ell_w + 2 L_w$, $\ell_w + 3 L_w$, $\ell_w + 4 L_w$, and so on.  These are the distances the ball has traveled when it bounces from the wall at $x = -r$ or at $x = w + r$.  (The first bounce occurs when the ball has traveled distance $\ell_w$, and subsequent bounces every $L_w$ of additional distance traveled.)
The other sequence is $\ell_h$, $\ell_h + L_h$, $\ell_h + 2 L_h$, $\ell_h + 3 L_h$, $\ell_h + 4 L_h$, and so on.  These are the distances the ball has traveled when it bounces from the wall at $y = -r$ or at $y = h + r$.  (The first bounce occurs when the ball has traveled distance $\ell_h$, and subsequent bounces every $L_h$ of additional distance traveled.)
We just need to interleave these two sequences in order of distance, to obtain the total distance traveled up to each bounce event.
I'm not a mathematician who can express such interleaving operations in proper terms, but I can show a Python scriptlet that does it:
# SPDX-License-Identifier: CC0-1.0

from math import sin, cos, pi, inf

class Bouncer:
    __slots__ = [ '_xdist', '_ydist', '_xstep', '_ystep' ]

    def __init__(self, x, y, degrees, w, h):
        if x < 0 or x > w or y < 0 or y > h:
            raise ValueError("Ball is placed outside the box")
        elif w <= 0 or h <= 0:
            raise ValueError("Degenerate box")

        theta = degrees * pi / 180.0
        dx = cos(theta)
        dy = sin(theta)

        # Traveling distance to initial x-wall
        if dx > 0.0:
            try:
                Cx = (w - x) / dx
            except ZeroDivisionError:
                Cx = inf
        elif dx < 0.0:
            try:
                Cx = x / -dx
            except ZeroDivisionError:
                Cx = inf
        else:
            Cx = inf

        # Traveling distance to initial y-wall
        if dy > 0.0:
            try:
                Cy = (h - y) / dy
            except ZeroDivisionError:
                Cy = inf
        elif dy < 0.0:
            try:
                Cy = y / -dy
            except ZeroDivisionError:
                Cy = inf
        else:
            Cy = inf

        # Traveling distance between consecutive x-walls
        try:
            Lx = w / abs(dx)
        except ZeroDivisionError:
            Lx = inf

        # Traveling distance between consecutive y-walls
        try:
            Ly = h / abs(dy)
        except ZeroDivisionError:
            Ly = inf

        # Internal state:
        self._xdist = Cx    # Distance at first x-wall bounce
        self._ydist = Cy    # Distance at first y-wall bounce
        self._xstep = Lx    # Distance between x-wall bounces
        self._ystep = Ly    # Distance between y-wall bounces

    def distance(self, bounces):
        b = 0
        d = 0
        x = self._xdist
        y = self._ydist

        while True:
            if b >= bounces:
                return d

            if x < y:
                d = x
                x += self._xstep
                b += 1
            elif y < x:
                d = y
                y += self._ystep
                b += 1
            else:
                # Corner bounce, count as a double bounce
                d = x
                x += self._xstep
                y += self._ystep
                b += 2

    def distances(self, bounces):
        b = 0
        d = 0
        x = self._xdist
        y = self._ydist

        while True:
            if b >= bounces:
                return

            if x < y:
                d = x
                x += self._xstep
                b += 1
                yield d
            elif y < x:
                d = y
                y += self._ystep
                b += 1
                yield d
            else:
                # Corner bounce, count as a double bounce
                d = x
                x += self._xstep
                y += self._ystep
                b += 2
                yield d
                if b < bounces:
                    yield d

Let's say we have a box $(-r,-r)-(4+r,3+r)$, the center of the $r$-radius ball initially at $(1,2)$, and heading $35°$:
b = Bouncer(1,2, 35, 4,3).
The distance at the tenth bounce is given by b.distance(10) ($23.1947$), and the distances to the first ten bounces by b.distances(10) ($1.7434$, $3.6623$, $6.9738$, $8.5454$, $12.2041$, $13.4285$, $17.4345$, $18.3116$, $22.6648$, and $23.1947$).  In other words, the ball travels a distance of $1.7434$ before the first bounce, then an additional distance of $3.6623-1.7434=1.9189$ to the second bounce, then an additional distance of $6.9738-3.6623=3.3115$ to the third bounce, at which point it has traveled a total distance of $6.9738$, and so on.
Interestingly, the same algorithm can be used to track intersections between a ray and unit lattice cells, which can be quite useful in many raycasting applications, especially because the iteration is just a conditional and one or more additions.  It can easily be modified to produce the intersection coordinates, also –– and similarly, the above code to produce the bounce coordinates and new directions.
A: I'm not sure if you're considering an ideal "point" ball, but that's pretty standard so I'm going to go with it. (If we did have a ball of radius $r$, we'd just subtract $2r$ from the dimensions of the rectangle before proceeding.)
Your idea of "unfolding" the box and making a grid of it is exactly what I would do! It makes things a lot easier. But we can make them even easier than that. First, I'm going to treat the Cartesian plane as if it were the complex plane, which makes writing coordinates easier. So we have a ball with some initial position $a+ib$, a rectangle of dimensions $c \times d$ (I hate when instructors or textbook use $x$ and $y$ as something other than variables), and an initial velocity:
$$f+ig = v \cos \theta + iv \sin \theta$$
Now a rectangle is just a square with shape dysphoria. We're going to take every horizontal quantity and divide by $c$, and divide all vertical quantities by $d$, and pretend our box is the unit square. We can rotate or reflect this square so that our path of travel points northeast, so that its bottom-left corner is at the origin, and so that the path exits the box somewhere on the right side, i.e., along the line $x=1$.
We now have a grid of unit squares to work with. That means that each of our bounces--including our endpoint--will occur where one coordinate or the other is an integer. Yay integers!
We'll renotate our transformed quantities: our initial position is now $j+ik$, and our initial velocity is $r+is$.
Consider what we know about lines, in general. If we move along a path with a known slope $m$, then if we move one unit to the right, we'll move $m$ units upward. If we move one unit upward, we'll move $1/m$ units to the right. Here, our slope is determined by initial velocity, and is $s/r$.
That means the ratio between horizontal grid lines we cross (horizontal bounces) and the vertical grid lines we cross (vertical bounces) is also the slope! Given that we want $n$ bounces total, we can get a sense of how many of each type of bounce we'll see by solving this system:
$$
\begin{cases}
\displaystyle\frac{b_h}{b_v} \approx \frac{s}{r} \\[0.7em]
n \approx b_v+b_h
\end{cases}
\implies
\begin{cases}
b_h \approx \displaystyle\frac{ns}{r+s} \\[0.5em]
b_v \approx \displaystyle\frac{nr}{r+s}
\end{cases}
$$
These are approximations; in reality, we'll need $b_h$ and $b_v$ to be integers. But they give us a good sense of how far we are likely to have to go in each direction. Since we've set ourselves up to exit the starting square on the right edge, our first bounce happens at $1+iy_0$ and is vertical. We expect around $b_v$ vertical bounces. So let's just... extend the path to $x=b_v$. We can calculate that point very easily; it's at $b_v + i(y_0 + b_vs/r) = b_v + iy_1$.
Since our first horiztonal bounce happened at $y=1$, sometime after we left the original square, $\lfloor y_1 \rfloor$ is the number of horizontal bounces the path has encountered.
There are now two possibilities: either $y_1 = b_h$ and our endpoint is revealed as $b_v + iy_1$, or not. If so, the path has a length of $\sqrt{c(b_h-j)^2 + d(y_1-k)^2}$. (Multiplying by $c$ and $d$ resets the division we performed early on.)
If not, we must either extend the path or backtrack. To extend, increase $y_1$ to the next integer and determine the appropriate $x$ coordinate and number of bounces; to backtrack, decrease to the previous integer, etc. This may take a jump or two, as it's possible that backtracking vertically also loses a horizontal bounce. I can't think of a good way to make this part into a closed-form expression quite yet, alas. Once you've found the correct point, again the distance formula gives you the path length, correcting with $c$ and $d$.
