Bringing a Gun to a Trainer Fight
Created: Oct 25, 2026
Last modified: Oct 25, 2026
TL, DR.
Bringing a Gun to a Trainer Fight
=================================
Uh-oh -- you've been cornered by one of Commander Lambdas elite bunny trainers! Fortunately, you grabbed a beam weapon from an abandoned storeroom while you were running through the station, so you have a chance to fight your way out. But the beam weapon is potentially dangerous to you as well as to the bunny trainers: its beams reflect off walls, meaning you'll have to be very careful where you shoot to avoid bouncing a shot toward yourself!
Luckily, the beams can only travel a certain maximum distance before becoming too weak to cause damage. You also know that if a beam hits a corner, it will bounce back in exactly the same direction. And of course, if the beam hits either you or the bunny trainer, it will stop immediately (albeit painfully).
Write a function solution(dimensions, your_position, trainer_position, distance) that gives an array of 2 integers of the width and height of the room, an array of 2 integers of your x and y coordinates in the room, an array of 2 integers of the trainer's x and y coordinates in the room, and returns an integer of the number of distinct directions that you can fire to hit the elite trainer, given the maximum distance that the beam can travel.
The room has integer dimensions [1 < x_dim <= 1250, 1 < y_dim <= 1250]. You and the elite trainer are both positioned on the integer lattice at different distinct positions (x, y) inside the room such that [0 < x < x_dim, 0 < y < y_dim]. Finally, the maximum distance that the beam can travel before becoming harmless will be given as an integer 1 < distance <= 10000.
For example, if you and the elite trainer were positioned in a room with dimensions [3, 2], your_position [1, 1], trainer_position [2, 1], and a maximum shot distance of 4, you could shoot in seven different directions to hit the elite trainer (given as vector bearings from your location): [1, 0], [1, 2], [1, -2], [3, 2], [3, -2], [-3, 2], and [-3, -2]. As specific examples, the shot at bearing [1, 0] is the straight line horizontal shot of distance 1, the shot at bearing [-3, -2] bounces off the left wall and then the bottom wall before hitting the elite trainer with a total shot distance of sqrt(13), and the shot at bearing [1, 2] bounces off just the top wall before hitting the elite trainer with a total shot distance of sqrt(5).
Languages
=========
To provide a Java solution, edit Solution.java
To provide a Python solution, edit solution.py
Test cases
==========
Your code should pass the following test cases.
Note that it may also be run against hidden test cases not shown here.
-- Java cases --
Input:
Solution.solution([3,2], [1,1], [2,1], 4)
Output:
7
Input:
Solution.solution([300,275], [150,150], [185,100], 500)
Output:
9
-- Python cases --
Input:
solution.solution([3,2], [1,1], [2,1], 4)
Output:
7
Input:
solution.solution([300,275], [150,150], [185,100], 500)
Output:
9This one is among my favorites. A bunny trainer has us cornered in a rectangular room, and we've got a beam weapon whose shots bounce off the walls. We have to count in how many directions we can fire so the beam reaches the trainer before it runs out of range, without hitting ourselves first. Every demo on this page uses the same room, the 4 × 3 one from the test cases with a range of 9. In any of them you can drag yourself and the trainer, but only inside the real room (the one in the middle when there are reflections), and every other demo follows along.
# A bounce is a reflection
Following a beam bounce by bounce is a pain. The trick is to stop moving the beam and move the room instead. When the beam hits the top wall and comes back down, it looks exactly like a beam that kept going straight into a reflection of the room, stuck on top of it like a mirror. The rooms above and below are reflections across a horizontal wall, the ones to the left and right across a vertical wall, and the diagonal ones are both at once: the reflection of a reflection.
Move the slider or press the button to add the reflections one by one, counterclockwise starting from the top. Drag you or the trainer inside the real room (the one in the middle) and watch their reflections move with them.
Legend: You The trainer
The real room: you at (1, 1), the trainer at (3, 2).
# Where the reflections land
Number the rooms with two indices: i counts columns (0 is the real room, 1 the one to its right, −1 the one to its left) and j counts rows the same way. Room (i, j) starts at x = i · w, and a point x inside the real room lands at the same distance from that edge… unless the room is mirrored. And a room is mirrored left to right when i is odd: an odd number of vertical walls in between. Even is two mirrors, which undo each other. The same goes for y with j and the height h:
In the code, that's the Room class. It only stores its indices and the size of the room, and
reflect takes a position of the real room (yours or the trainer's) and returns where it shows up in that copy.
Since you and the trainer go through the exact same formula, the whole plane fills up with copies of both.
# A straight line that bounces
With the reflections in place, a shot is just a straight line from you. Each time it crosses a wall line in the plane, the real beam bounces off that wall. To go back, you fold the plane over the real room: every piece of the line between two wall lines lands inside the room, mirrored as many times as needed. Turn the beam with the slider (or press the button to give it a full turn) and compare the straight line on the left with the bounces on the right (the line stops at the edge of the eight reflections so it doesn't bounce forever).
At 30° the beam bounces 2 times before leaving the reflections.
Even the strangest rule in the statement falls out on its own: a beam that hits a corner bounces back the way it came. In the plane, crossing a corner means crossing a vertical and a horizontal wall line at the same time, so the folded beam is mirrored both ways, which is the same as turning it around.
# The range decides how many rooms
There are infinitely many reflections, but the beam doesn't go on forever. It can travel distance units at most,
so everything it can hit is inside a circle of that radius around you. In the x direction that means from
x − distance to x + distance, and the rooms covering that span go from
i = ⌊(x − distance) / w⌋ to ⌊(x + distance) / w⌋.
Same thing for j. That's a rectangle of rooms around the circle; the images in its corners that fall outside the
circle get thrown away.
To throw them away there's no need for a square root. Comparing two distances is the same as comparing their squares, so it's enough to check $dx^2 + dy^2 \le distance^2$, all in integers. That idea comes from the Princeton algorithms course on Coursera, by Robert Sedgewick and Kevin Wayne, which I took back then: they always suggested avoiding square roots when you only need to compare.
# Who gets hit first
Every image of the trainer inside the circle is a candidate shot: a straight line from you to it. But a beam stops at the first thing it hits, and that might be one of your own reflections, which means the beam came back to you. Or it might be another image of the trainer that's closer on that same line, and then both shots are the same direction, counted once. What we need is to find the points that are collinear with you, on the same ray, and keep only the nearest one.
That's where the same course helped again. One of its assignments was finding collinear points, and the idea was to sort all the points by the angle they make with a reference point: the ones that are lined up end up next to each other. Here the reference is you. Sort every image by its angle and, on the same ray, by distance, and then walk the list: the first image of each ray is the one the beam hits. If it's the trainer, that direction counts. If it's you, it doesn't, and neither does anything behind it.
One detail to tell rays apart exactly: an image at (4, 2) from you and another at (2, 1) are on the same ray, but floating
point angles could come out a hair different. So each image stores its bearing reduced by the greatest common divisor:
both become (2, 1), and comparing two pairs of integers never fails. That's the Image class: whether
it's the trainer or you, its squared distance and its bearing. The slider below walks the rays in the order the code finds
them, starting from the left (−180°) and going around counterclockwise.
Legend: You The trainer Beam that reaches the trainer first Beam that reaches you first How far the beam reaches
41 images fall inside the circle, and they line up on 34 different rays. Move the slider or press the button to sweep them.
# Every shot, back in the room
Fold each winning ray back into the real room and you get the actual shots, bounces and all. With you at (1, 1) and the trainer at (3, 2), as in the tests, there are 19. At 0 the slider shows all of them; move it or press the button to see them one at a time, in the same order as before.
All 19 shots that reach the trainer before anything else.
# The code
I actually had two versions, and both passed. The first one built the reflections like a search: start from the real room, add its four neighbors (above, below, left, right), then theirs, until they fell out of range, and sort à la Sedgewick. The second computed the rooms directly from the distance, but compared angles as floating point numbers and used square roots. This one keeps the best of each:
Room reflects a position of the real room into copy (i, j): odd columns flip
x, odd rows flip y.
Image is a copy of you or the trainer seen from the real you: its squared distance and its reduced bearing,
plus the angle of that bearing for sorting.
solution goes through the rooms that cover the range, keeps the images within distance
(the real you is skipped: that's where the beam starts), sorts them by angle and distance, and counts the rays whose
first image is the trainer.
from math import atan2
class Room:
"""A copy of the room in the mirrored plane: (0, 0) is the real one, (1, 0) the one behind its right wall."""
def __init__(self, dimensions, i, j):
self.width, self.height = dimensions
self.i, self.j = i, j
def reflect(self, position):
"""Where a point of the real room shows up in this copy."""
x, y = position
if self.i % 2 == 1:
x = self.width - x # odd columns are mirrored left to right
if self.j % 2 == 1:
y = self.height - y # odd rows are mirrored top to bottom
return self.i * self.width + x, self.j * self.height + y
class Image:
"""A reflected you or trainer, as seen from the real you."""
def __init__(self, point, origin, is_trainer):
self.is_trainer = is_trainer
dx, dy = point[0] - origin[0], point[1] - origin[1]
# Squared: comparing distances doesn't need the square root
self.distance = dx * dx + dy * dy
# The bearing reduced to its smallest form, so every point on the same ray gets the same one
g = gcd(abs(dx), abs(dy))
self.bearing = (dx // g, dy // g)
self.angle = atan2(self.bearing[1], self.bearing[0])
def gcd(a, b):
while b:
a, b = b, a % b
return a
def solution(dimensions, your_position, trainer_position, distance):
width, height = dimensions
x, y = your_position
# Every copy of the room that the beam can reach
images = []
for i in range((x - distance) // width, (x + distance) // width + 1):
for j in range((y - distance) // height, (y + distance) // height + 1):
room = Room(dimensions, i, j)
for position, is_trainer in ((your_position, False), (trainer_position, True)):
if (i, j) == (0, 0) and not is_trainer:
continue # the real you is where the beam starts, not a target
image = Image(room.reflect(position), your_position, is_trainer)
if image.distance <= distance * distance:
images.append(image)
# Sweep around you: by angle, and on the same ray, nearest first
images.sort(key=lambda image: (image.angle, image.distance))
directions = 0
for k, image in enumerate(images):
first_on_ray = k == 0 or images[k - 1].bearing != image.bearing
if first_on_ray and image.is_trainer:
directions += 1
return directions
Two details. The range is inclusive: a trainer exactly distance away still gets hit, hence the <=.
And (x - distance) // width relies on Python's // rounding down even with negative numbers, so
−1 // 4 is −1, not 0. In Java or C#, integer division rounds toward zero, and that would skip a column of rooms.
# Wrapping up
The statement is all bounces, corners and beams that come back to you, and none of that needs to be simulated. Mirroring the room turns every bounce into a straight line, the range turns infinitely many rooms into a handful, and sorting by angle turns "what does the beam hit first" into "who comes first on each ray". It's the kind of problem where the right change of perspective does all the work.
