Given a maze, start coordinate, and end coordinate,
maze = [
[BLACK, BLACK, WHITE, WHITE],
[BLACK, WHITE, WHITE, WHITE],
[WHITE, WHITE, BLACK, WHITE],
[WHITE, WHITE, BLACK, WHITE],
]
s = Coordinate(3, 0)
e = Coordinate(0, 3)
I am trying to find the path from start to end using BFS.
Finding the path is straightforward, but I am struggling with keeping the path to the destination.
What I've tried is
directions = [(1, 0), (0, 1), (-1, 0), (0, -1)]
queue = collections.deque()
queue.append(s)
path = []
while queue:
curr = queue.popleft()
if curr == e:
path.append(curr)
return path
path.append(curr)
maze[curr.x][curr.y] = BLACK
for x, y in directions:
new_x, new_y = curr.x + x, curr.y + y
if new_x < 0 or new_y < 0 or new_x >= len(maze) or new_y >= len(maze[0]) or maze[new_x][new_y] == BLACK:
continue
queue.append(Coordinate(new_x, new_y))
Something like this, but the result prints out all the nodes that I've visited, instead of the final path. Any tips on keeping the right path and removing the node that does not belong to the final path?
Instead of maintaining a path list, you can maintain an edge_to dictionary which keeps track of which previous vertex led to visiting a certain vertex. Whenever you add something to your queue, you can can update edge_to. A modified version of your function using this approach is as follows:
Coordinate = collections.namedtuple('Coordinate', ['x', 'y'])
def find_path(s, e):
directions = [(1, 0), (0, 1), (-1, 0), (0, -1)]
queue = collections.deque()
queue.append(s)
edge_to = {s: None}
while queue:
curr = queue.popleft()
if curr == e:
return path(edge_to, curr)
maze[curr.x][curr.y] = BLACK
for x, y in directions:
new_x, new_y = curr.x + x, curr.y + y
if new_x < 0 or new_y < 0 or new_x >= len(maze) or new_y >= len(maze[0]) or maze[new_x][new_y] == BLACK:
continue
c = Coordinate(new_x, new_y)
edge_to[c] = curr
queue.append(c)
Notice the call to path(...) when you find your end vertex. That function just builds a list from the edge_to dictionary:
def path(edge_to, end):
curr = end
res = []
while curr != None:
res.append(curr)
curr = edge_to[curr]
return list(reversed(res))
For your given maze, and start and end coordinates, we get the following output:
s = Coordinate(3, 0)
e = Coordinate(0, 3)
print(find_path(s, e))
Output
[Coordinate(x=3, y=0), Coordinate(x=2, y=0), Coordinate(x=2, y=1), Coordinate(x=1, y=1), Coordinate(x=1, y=2), Coordinate(x=0, y=2), Coordinate(x=0, y=3)]
for x, y in directions:
new_x, new_y = curr.x + x, curr.y + y
if new_x < 0 or new_y < 0 or new_x >= len(maze) or new_y >= len(maze[0]) or maze[new_x][new_y] == BLACK:
continue
queue.append(Coordinate(new_x, new_y))
your queue.append(Coordinate(new_x, new_y)) is running everytime your for loops iterates.
We want this condition to only happen when our if condition runs, so lets try something like this:
for x, y in directions:
new_x, new_y = curr.x + x, curr.y + y
if new_x < 0 or new_y < 0 or new_x >= len(maze) or new_y >= len(maze[0]) or maze[new_x][new_y] == BLACK:
queue.append(Coordinate(new_x, new_y))
continue
When our if-condition is met, append it, and then continue. Let me know if this helps.
Related
I have to write this function that generates a game tree for Othello (or Reversi), without using any library, that at the end gives me as an output a tuple (a,b,c) with:
a: the number of situations that result in a black win
b: the number of situations that result in a white win
c: the number of situations that result in a draw.
I have to work with these boards given as a txt file, as such:
. . W W
. . B B
W W W B
W B B W
The problem is I get a wrong output (different from the expected one). In the given exemple above, the output tuple should be (2, 16, 0), but I get (5, 7, 11). Below I will leave you the code, and I can't figure out what did I do wrong.
def generate_game_tree(filename: str):
# Load the board from the file
board = [line.split() for line in open(filename)]
# Initialize the game tree with the root node representing the current board state
game_tree = [(board, 0)]
black_count = 0
white_count = 0
draw_count = 0
# Generate the game tree by expanding the nodes in a breadth-first manner
i = 0
while i < len(game_tree):
node = game_tree[i]
board, _ = node
valid_moves = get_valid_moves(board)
for move in valid_moves:
new_board = make_move(board, move)
game_tree.append((new_board, i))
i += 1
for i in range(len(board)):
for j in range(len(board[0])):
if board[i][j] == "W":
white_count += 1
if board[i][j] == "B":
black_count += 1
else:
draw_count += 1
return black_count, white_count, draw_count
def make_move(board, move):
flips = []
x, y = move
curr_color = board[x][y]
for dx, dy in [(1, 0), (1, 1), (0, 1), (-1, 1), (-1, 0), (-1, -1), (0, -1), (1, -1)]:
x, y = move
x += dx
y += dy
if not (0 <= x < len(board) and 0 <= y < len(board[0])):
continue
if board[x][y] != '.':
continue
while board[x][y] != curr_color:
flips.append((x, y))
x += dx
y += dy
if not (0 <= x < len(board) and 0 <= y < len(board[0])):
break
return flips
def get_valid_moves(board):
valid_moves = []
for i in range(len(board)):
for j in range(len(board[0])):
if board[i][j] != '.':
continue
flips = make_move(board, (i, j))
if flips:
valid_moves.append((i, j))
return valid_moves
Let's say I have a two dimensional grid of 10x10 cells. The top left cell has coordinates (0,0) and
the bottom right cell has coordinates (9,9).
The code below doesn't seem to function the way I want it to.
I can't figure out what I am doing wrong.
'''
X = 10
Y = 10
class Cell:
def __init__(self,x,y) -> None:
self.coordinates = (x,y)
self.neigbors = self.find_neighbors()
def find_neighbors(self):
x,y = self.coordinates
neighbors = [
(x+1,y),(x-1,y),(x,y+1),(x,y-1),(x+1,y+1),
(x+1,y-1),(x-1,y+1),(x-1,y-1)
]
for neighbor in neighbors:
if neighbor[0] < 0 or neighbor[1] < 0:
neighbors.remove(neighbor)
elif neighbor[0] >= X or neighbor[1] >= Y:
neighbors.remove(neighbor)
return neighbors
cell1 = Cell(0,0)
cell1.neigbors
# [(1, 0), (0, 1), (1, 1), (-1, 1)]
# shouldn't have (-1,1)
cell2 = Cell(9,9)
cell2.neigbors
# [(8, 9), (9, 8), (10, 8), (8, 8)]
# shouldn't have (10,8)
'''
Better not to remove items from a list while iterating over it (as already pointed out in the comments). Here's an idea where you mark the entries in the list of potential coordinates as "unwanted" then subsequently reconstruct the desired output:
MX = 10
MY = 10
def neighbours(x, y):
pn = [(x-1, y), (x+1, y), (x-1, y-1), (x, y-1),
(x+1, y-1), (x-1, y+1), (x, y+1), (x+1, y+1)]
for i, t in enumerate(pn):
if t[0] < 0 or t[1] < 0 or t[0] >= MX or t[1] >= MY:
pn[i] = None
return [c for c in pn if c is not None]
print(neighbours(5, 4))
Instead of the for-loop put this piece.
neighbors = [neighbor for neighbor in neighbors if validate_cell(neighbor)]
The function validate_cell(coordinate).
validate_cell(coordinate):
if coordinate[0] < 0 or coordinate[1] < 0:
return False
elif coordinate[0] >= X or coordinate[1] >= Y:
return False
else:
return True
I've been working on some introductory Graph Theory lately, and came across the following problem statement:
Given a 2D matrix as input, representing a maze (where 'E' is the start of the matrix and 'S' is the end), find the length of the shortest path from E to S. Walls in the maze are represented by '#', while accessible spaces are indicated with '.'. It's also a given that the outer edge of the matrix is covered with walls. If no path exists from E to S, return -1.
Since the graph isn't weighted, I tried implementing a BFS algorithm, using deque. However, when the mazes start hitting around 500x500, execution time hits 10s, and when I try to go up to 1000x1000, it's obviously a lot worse.
Here's my code:
from collections import deque
def shortest_path_1(maze):
wall, clear, endchar, startchar = '#', '.', 'S', 'E'
height = len(maze)
width = len(maze[0])
def find_start(grid):
for y in range(1, height-1):
for x in range(1, width-1):
if grid[y][x] == startchar:
return tuple([x, y])
start = find_start(maze)
queue = deque([[start]])
seen = {start}
while queue:
path = queue.popleft()
x, y = path[-1]
if maze[y][x] == endchar:
return len(path)-1
for (x2, y2) in ((x + 1, y), (x - 1, y), (x, y + 1), (x, y - 1)):
if 0 < x2 < width-1 and 0 < y2 < height-1 and maze[y2][x2] != wall and (x2, y2) not in seen:
queue.append(path + [(x2, y2)])
seen.add((x2, y2))
return -1
I found some very useful answers on the site so far, but none of the current ones seem to give any other optimizations that I haven't implemented yet...
Thanks!
EDIT: Thanks to the lovely person who edited my question to make the key words pop :). Here's an example of a matrix that you can run the algorithm on:
#####
#E#S#
#.#.#
#...#
#####
This should return the value 6.
EDIT2: Fixed some small mistakes.
As suggested in the comments, you don't have to store the paths. Try this:
from collections import deque
def shortest_path_1(maze):
wall, clear, endchar, startchar = '#', '.', 'S', 'E'
height = len(maze)
width = len(maze[0])
def find_start(grid):
for y in range(1, height-1):
for x in range(1, width-1):
if grid[y][x] == startchar:
return (x, y, 0)
start = find_start(maze)
queue = deque([start])
seen = set()
while queue:
x, y, d = queue.popleft()
if not 0 <= x < width:
continue
if not 0 <= y < height:
continue
if maze[y][x] == wall:
continue
if maze[y][x] == endchar:
return d
if (x, y) in seen:
continue
seen.add((x, y))
queue.append((x+1, y, d+1))
queue.append((x-1, y, d+1))
queue.append((x, y+1, d+1))
queue.append((x, y-1, d+1))
return -1
maze = [x for x in """
#####
#E#S#
#.#.#
#...#
#####
""".split('\n') if x]
print shortest_path_1(maze)
You are confronted with an enemy within a rectangular shaped room and you've got only a laser beam weapon, the room has no obstructions in it and the walls can completely reflect the laser beam. However the laser can only travels a certain distance before it become useless and if it hit a corner it would reflect back in the same direction it came from.
That's how the puzzle goes and you are given the coordinates of your location and the target's location, the room dimensions and the maximum distance the beam can travel. for example If the room is 3 by 2 and your location is (1, 1) and the target is (2, 1) then the possible solutions are:
I tried the following approach, start from the source (1, 1) and create a vector at angle 0 radians, trace the vector path and reflections until either it hits the target or the total length of the vectors exceeds the max allowed length, repeat with 0.001 radians interval until it completes a full cycle. This the code I have so far:
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's 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(v.n)n
r = (vx * (1 - 2 * nx * nx), vy * (1 - 2 * ny * ny))
# calculating the angle of the reflection vector using it's 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 doesn't find all the possible solutions, I have a big precision problem in it, I dont' know how many decimals should I consider when comparing distances or angles. I'm not sure it's the right way to do it but that's the best I was able to do.
How can I fix my code to extract all solutions? I need it to be efficient because the room can get quite large (500 x 500). Is there a better way or maybe some sort of algorithm to do this?
what if you started by mirroring the target at all the walls; then mirror the mirror images at all the walls and so on until the distance gets too big for the laser to reach the target? any laser shot in any direction of a target mirrored that way will hit said target. (this is my comment from above; repeated here to make answer more self-contained...)
this is the mirroring part of the answer: get_mirrored will return the four mirror images of point with the mirror-box limited by 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 the 4 mirror images of the given point:
[(2, 3), (2, -1), (-2, 1), (4, 1)]
which is the target mirrored one time.
then you could iterate that until all the mirrored targets are out of range. all the mirror images within range will give you a direction in which to point your laser.
this is a way how you could iteratively get to the mirrored targets within 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 is now the set that contains all the reachable points.
(none of that has been thouroughly tested...)
small update for your counter example:
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
I have attempted to make Conway's game of life in python, and then save the output into a picture, but I think there is something wrong with the logic as most of the pictures don't look quite correct. (see picture)
game of life pic:
import PIL.Image, random
WIDTH = 1366
HEIGHT = 768
ROUNDS = 10
DEAD = (0, 0, 0)
ALIVE = (0, 64, 255)
print("Creating image")
img = PIL.Image.new("RGB", (WIDTH, HEIGHT))
data = img.load()
print("Creating grid")
grid = []
for y in range(HEIGHT):
grid.append([])
for x in range(WIDTH):
grid[y].append(random.randint(0, 1))
for i in range(ROUNDS):
print("Starting round", i + 1, "of", ROUNDS)
for y in range(HEIGHT):
for x in range(WIDTH):
n = 0
for y2 in range(-1, 2):
for x2 in range(- 1, 2):
if x2 != 0 and y2 != 0 and grid[(y + y2) % HEIGHT][(x + x2) % WIDTH] == 1:
n += 1
if n < 2:
grid[y][x] = 0
elif n > 3:
grid[y][x] = 0
elif grid[y][x] == 1 and n > 1 and n < 4:
grid[y][x] = 1
elif grid[y][x] == 0 and n == 3:
grid[y][x] = 1
print("Rendering image")
for y in range(HEIGHT):
for x in range(WIDTH):
if grid[y][x] == 1:
data[x, y] = ALIVE
else:
data[x, y] = DEAD
print("Saving image")
img.save("gofl.png")
Your program cannot work correctly in its current state, because you compute the next generation in the same grid where the last generation is stored. You need a new (empty) grid to store the next generation. In your implementation you overwrite the last generation already while computing the next generation.