Puzzle: how many ways you can hit a target with a laser beam in four reflective walls

You are faced with an enemy in a rectangular shape, and you only have weapons with a laser beam, there are no obstacles in the room, and the walls can completely reflect the laser beam. However, the laser can only move a certain distance before it becomes useless, and if it hits a corner, it will bounce back in the same direction in which it appeared.

The way the puzzle goes, and you are given the coordinates of your location and the target location, the size of the room and the maximum distance that the beam can move. for example, if room 3 is 2, and your location is (1, 1), and your goal is (2, 1), then possible solutions are:

All possible ways to hit the target (2, 1) from (1, 1) in a 3x2 room

I tried the following approach, starting from the source (1, 1) and creating a vector at an angle of 0 radians, it traces the vector path and reflections until it reaches the goal or the total length of the vectors exceeds the maximum length, repeat with an interval of 0.001 radian until he completes the full cycle. This is the code that I still have:

from math import * UPRIGHT = 0 DOWNRIGHT = 1 DOWNLEFT = 2 UPLEFT = 3 UP = 4 RIGHT = 5 LEFT = 6 DOWN = 7 def roundDistance (a): b = round (a * 100000) return b / 100000.0 # only used for presenting and doesn't affect percision def double (a): b = round (a * 100) if b / 100.0 == b: return int (b) return b / 100.0 def roundAngle (a): b = round (a * 1000) return b / 1000.0 def isValid (point): x,y = point if x < 0 or x > width or y < 0 or y > height: return False return True def isCorner (point): if point in corners: return True return False # Find the angle direction in relation to the origin (observer) point def getDirection (a): angle = roundAngle (a) if angle == 0: return RIGHT if angle > 0 and angle < pi / 2: return UPRIGHT if angle == pi / 2: return UP if angle > pi / 2 and angle < pi: return UPLEFT if angle == pi: return LEFT if angle > pi and angle < 3 * pi / 2: return DOWNLEFT if angle == 3 * pi / 2: return DOWN return DOWNRIGHT # Measure reflected vector angle def getReflectionAngle (tail, head): v1 = (head[0] - tail[0], head[1] - tail[1]) vx,vy = v1 n = (0, 0) # Determin the normal vector from the tail position on the borders if head[0] == 0: n = (1, 0) if head[0] == width: n = (-1, 0) if head[1] == 0: n = (0, 1) if head[1] == height: n = (0, -1) nx,ny = n # Calculate the reflection vector using the formula: # r = v - 2(vn)n r = (vx * (1 - 2 * nx * nx), vy * (1 - 2 * ny * ny)) # calculating the angle of the reflection vector using it a and b values # if b (adjacent) is zero that means the angle is either pi/2 or -pi/2 if r[0] == 0: return pi / 2 if r[1] >= 0 else 3 * pi / 2 return (atan2 (r[1], r[0]) + (2 * pi)) % (2 * pi) # Find the intersection point between the vector and borders def getIntersection (tail, angle): if angle < 0: print "Negative angle: %f" % angle direction = getDirection (angle) if direction in [UP, RIGHT, LEFT, DOWN]: return None borderX, borderY = corners[direction] x0,y0 = tail opp = borderY - tail[1] adj = borderX - tail[0] p1 = (x0 + opp / tan (angle), borderY) p2 = (borderX, y0 + adj * tan (angle)) if isValid (p1) and isValid (p2): print "Both intersections are valid: ", p1, p2 if isValid (p1) and p1 != tail: return p1 if isValid (p2) and p2 != tail: return p2 return None # Check if the vector pass through the target point def isHit (tail, head): d = calcDistance (tail, head) d1 = calcDistance (target, head) d2 = calcDistance (target, tail) return roundDistance (d) == roundDistance (d1 + d2) # Measure distance between two points def calcDistance (p1, p2): x1,y1 = p1 x2,y2 = p2 return ((y2 - y1)**2 + (x2 - x1)**2)**0.5 # Trace the vector path and reflections and check if it can hit the target def rayTrace (point, angle): path = [] length = 0 tail = point path.append ([tail, round (degrees (angle))]) while length < maxLength: head = getIntersection (tail, angle) if head is None: #print "Direct reflection at angle (%d)" % angle return None length += calcDistance (tail, head) if isHit (tail, head) and length <= maxLength: path.append ([target]) return [path, double (length)] if isCorner (head): #print "Corner reflection at (%d, %d)" % (head[0], head[1]) return None p = (double (head[0]), double (head[1])) path.append ([p, double (degrees (angle))]) angle = getReflectionAngle (tail, head) tail = head return None def solve (w, h, po, pt, m): # Initialize global variables global width, height, origin, target, maxLength, corners, borders width = w height = h origin = po target = pt maxLength = m corners = [(w, h), (w, 0), (0, 0), (0, h)] angle = 0 solutions = [] # Loop in anti-clockwise direction for one cycle while angle < 2 * pi: angle += 0.001 path = rayTrace (origin, angle) if path is not None: # extract only the points coordinates route = [x[0] for x in path[0]] if route not in solutions: solutions.append (route) print path # Anser is 7 solve (3, 2, (1, 1), (2, 1), 4) # Answer is 9 #solve (300, 275, (150, 150), (185, 100), 500) 

The code works somehow, but it does not find all possible solutions, I have a big problem with accuracy, I don’t know how many decimal places to consider when comparing distances or angles. I'm not sure if this is the right way to do this, but this is the best I could do.

How can I fix my code to extract all the solutions? I need this to be effective because the room can become quite large (500 x 500). Is there a better way or maybe some kind of algorithm for this?

+5
source share
1 answer

what if you started by mirroring the target on all walls; then mirror the mirrored images on all walls, and so on, until the distance becomes too large for the laser to reach the target? any laser image in any direction of the target, reflected in this way, will hit the specified target. (this is my comment above, repeated here to make the answer more self-sufficient ...)

this is the mirror part of the answer: get_mirrored will return four mirror image point with a mirror field limited to BOTTOM_LEFT and TOP_RIGHT .

 BOTTOM_LEFT = (0, 0) TOP_RIGHT = (3, 2) SOURCE = (1, 1) TARGET = (2, 1) def get_mirrored(point): ret = [] # mirror at top wall ret.append((point[0], point[1] - 2*(point[1] - TOP_RIGHT[1]))) # mirror at bottom wall ret.append((point[0], point[1] - 2*(point[1] - BOTTOM_LEFT[1]))) # mirror at left wall ret.append((point[0] - 2*(point[0] - BOTTOM_LEFT[0]), point[1])) # mirror at right wall ret.append((point[0] - 2*(point[0] - TOP_RIGHT[0]), point[1])) return ret print(get_mirrored(TARGET)) 

this will return 4 mirror images of the given point:

 [(2, 3), (2, -1), (-2, 1), (4, 1)] 

which is a mirror image at one time.

then you can repeat this until all the mirror objects are out of range. all mirror images within the range will give you the direction in which the laser can be pointed.


this is the way in which you could iteratively get to mirror targets in a given DISTANCE

 def get_targets(start_point, distance): all_targets = set((start_point, )) # will also be the return value last_targets = all_targets # need to memorize the new points while True: new_level_targets = set() # if this is empty: break the loop for tgt in last_targets: # loop over what the last iteration found new_targets = get_mirrored(tgt) # only keep the ones within range new_targets = set( t for t in new_targets if math.hypot(SOURCE[0]-t[0], SOURCE[1]-t[1]) <= DISTANCE) # subtract the ones we already have new_targets -= all_targets new_level_targets |= new_targets if not new_level_targets: break # add the new targets all_targets |= new_level_targets last_targets = new_level_targets # need these for the next iteration return all_targets DISTANCE = 5 all_targets = get_targets(start_point=TARGET, distance=DISTANCE) print(all_targets) 

all_targets now a set that contains all the available points.

(not one of them has been verified ...)


A small update for your example counter:

 def ray_length(point_list): d = sum((math.hypot(start[0]-end[0], start[1]-end[1]) for start, end in zip(point_list, point_list[1:]))) return d d = ray_length(point_list=((1,1),(2.5,2),(3,1.67),(2,1))) print(d) # -> 3.605560890844135 d = ray_length(point_list=((1,1),(4,3))) print(d) # -> 3.605551275463989 
+3
source

Source: https://habr.com/ru/post/1261910/


All Articles