Algorithm Visualizer DFS Implementation Error - python

The algorithm searches straight upwards and when it reaches the barrier, it just stops, instead of going to the right
Here.
I am new to graph-algorithms and have just learned about them, so I am clueless when it comes to implementing them.
My code is not optimized, however, I will optimize it later. Here is my code:
import pygame
import queue
WIDTH = 720
pygame.init()
WIN = pygame.display.set_mode((WIDTH, WIDTH))
pygame.display.set_caption("Pathfinding Visualizer")
RED = (255, 0, 0)
GREEN = (0, 255, 0)
BLUE = (0, 255, 0)
YELLOW = (255, 255, 0)
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
PURPLE = (128, 0, 128)
ORANGE = (255, 165 ,0)
GREY = (128, 128, 128)
TURQUOISE = (64, 224, 208)
class Node:
def __init__(self, row, col, width, total_rows):
self.row = row
self.col = col
self.width = width
self.x = row * width
self.y = col * width
self.color = WHITE
self.neighbours = []
self.total_rows = total_rows
def get_pos(self):
return self.row, self.col
def is_closed(self):
return self.color == RED
def is_open(self):
return self.color == GREEN
def is_barrier(self):
return self.color == BLACK
def is_start(self):
return self.color == ORANGE
def is_end(self):
return self.color == PURPLE
def reset(self):
self.color = WHITE
def make_start(self):
self.color = ORANGE
def make_closed(self):
self.color = RED
def make_open(self):
self.color = GREEN
def make_barrier(self):
self.color = BLACK
def make_end(self):
self.color = TURQUOISE
def make_path(self):
self.color = PURPLE
def draw(self, win):
pygame.draw.rect(win, self.color, (self.x, self.y, self.width, self.width))
def update_neighbours(self, grid):
self.neighbours = []
if self.row < self.total_rows - 1 and not grid[self.row + 1][self.col].is_barrier(): # DOWN
self.neighbours.append(grid[self.row + 1][self.col])
if self.row > 0 and not grid[self.row - 1][self.col].is_barrier(): # UP
self.neighbours.append(grid[self.row - 1][self.col])
if self.col < self.total_rows - 1 and not grid[self.row][self.col + 1].is_barrier(): # RIGHT
self.neighbours.append(grid[self.row][self.col + 1])
if self.col > 0 and not grid[self.row][self.col - 1].is_barrier(): # LEFT
self.neighbours.append(grid[self.row][self.col - 1])
def __lt__(self, other):
return False
def h(p1, p2):
x1, y1 = p1
x2, y2 = p2
return abs(x1 - x2) + abs(y1 - y2)
def reconstruct_path(came_from, current, draw):
print(came_from)
while current in came_from:
current = came_from[current]
current.make_path()
draw()
def dfs(draw, grid, start, end):
open_set = queue.LifoQueue()
open_set.put(start)
came_from = {}
g = {node: float("inf") for row in grid for node in row}
g[start] = 0
open_set_hash = {start}
while not open_set.empty():
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_c:
return False
current = open_set.get()
open_set_hash.remove(current)
if current == end:
reconstruct_path(came_from, end, draw)
end.make_end()
start.make_start()
return True, g
for n in current.neighbours:
g[n] = g[current] + 1
came_from[n] = current
if n not in open_set_hash:
open_set.put(n)
open_set_hash.add(n)
draw()
if current != start:
current.make_closed()
return False
def astar(draw, grid, start, end):
count = 0
open_set = queue.PriorityQueue()
open_set.put(item=(0, count, start))
came_from = {}
g = {node: float("inf") for row in grid for node in row}
g[start] = 0
f = {node: float("inf") for row in grid for node in row}
f[start] = h(start.get_pos(), end.get_pos())
open_set_hash = {start}
while not open_set.empty():
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_c:
return False
current = open_set.get()[2]
open_set_hash.remove(current)
if current == end:
reconstruct_path(came_from, end, draw)
end.make_end()
start.make_start()
return True, g
for neighbour in current.neighbours:
temp_g = g[current] + 1
if temp_g < g[neighbour]:
came_from[neighbour] = current
g[neighbour] = temp_g
f[neighbour] = temp_g + h(neighbour.get_pos(), end.get_pos())
if neighbour not in open_set_hash:
count += 1
open_set.put((f[neighbour], count, neighbour))
open_set_hash.add(neighbour)
neighbour.make_open()
draw()
if current != start:
current.make_closed()
return False
def make_grid(rows, width):
grid = []
gap = width // rows
for i in range(rows):
# If rows = 5, then grid would be [[], [], [], [], []]
grid.append([])
for j in range(rows):
node = Node(i, j, gap, rows)
if i == 0 or j == 0 or i == 59 or j == 59:
node.make_barrier()
grid[i].append(node)
return grid
def draw_grid(win, rows, width):
gap = width // rows
for i in range(rows):
pygame.draw.line(win, GREY, (0, i*gap), (width, i*gap))
for j in range(rows):
pygame.draw.line(win, GREY, (j*gap, 0), (j*gap, width))
def draw_text(win):
font = pygame.font.Font(None, 20)
text1 = font.render("DepthFirstSearch - D", 1, (255, 255, 255))
text2 = font.render("BreadthFirstSearch - B", 1, (255, 255, 255))
text3 = font.render("AStar - A", 1, (255, 255, 255))
textpos1 = text1.get_rect()
textpos2 = text2.get_rect()
textpos3 = text3.get_rect()
textpos1.centerx = win.get_rect().centerx-290
textpos2.centerx = win.get_rect().centerx-125
textpos3.centerx = win.get_rect().centerx+10
win.blit(text1, textpos1)
win.blit(text2, textpos2)
win.blit(text3, textpos3)
def draw(win, grid, rows, width):
win.fill(WHITE)
for row in grid:
for node in row:
node.draw(win)
draw_grid(win, rows, width)
draw_text(win)
pygame.display.update()
def get_clicked_pos(pos, rows, width):
gap = width // rows
y, x = pos
row = y // gap
col = x // gap
return row, col
def main(win, width):
ROWS = 60
grid = make_grid(ROWS, width)
start = None
end = None
run = True
started = False
while run:
draw(win, grid, ROWS, width)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
if started:
continue
if pygame.mouse.get_pressed()[0]: # LEFT
pos = pygame.mouse.get_pos()
row, col = get_clicked_pos(pos, ROWS, width)
node = grid[row][col]
if not start and node != end and node.is_barrier() == False:
start = node
start.make_start()
elif not end and node != start and node.is_barrier() == False:
end = node
end.make_end()
elif node != end and node != start:
node.make_barrier()
elif pygame.mouse.get_pressed()[2]: # RIGHT
pos = pygame.mouse.get_pos()
row, col = get_clicked_pos(pos, ROWS, width)
node = grid[row][col]
node.reset()
if node == start:
start = None
elif node == end:
end = None
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_a and start and end:
for row in grid:
for node in row:
node.update_neighbours(grid)
astar(lambda: draw(win, grid, ROWS, width), grid, start, end)
elif event.key == pygame.K_d and start and end:
for row in grid:
for node in row:
node.update_neighbours(grid)
dfs(lambda: draw(win, grid, ROWS, width), grid, start, end)
elif event.key == pygame.K_b and start and end:
for row in grid:
for node in row:
node.update_neighbours(grid)
# bfs(lambda: draw(win, grid, ROWS, width), grid, start, end)
if event.key == pygame.K_c:
start = None
end = None
grid = make_grid(ROWS, width)
pygame.quit()
main(WIN, WIDTH)

I commented out your open_set_hash.remove(current). I don't see why you remove the current from the visited list? Also moved the came_from[n] = current inside the if condition. This was to get the reconstruct_path function to work properly.
if n not in open_set_hash:
came_from[n] = current
Looks like it works just fine after this. BTW. nice visualization you made!

Related

How to generate my maze instantly so I don't have to watch it Generate?

So I'm creating a game and I'm using Recursive backtracking algorithm to create the maze, however, I don't want it to show the maze generation and just to instantly generate the maze. I'm unsure of how to actually do this though so any help would be appreciated, I've already tried not drawing the generated white part but that then doesn't create the maze.
import pygame
import random
import time
class Cell(object):
def __init__(self, x, y, cell_size, screen, black, white, red, blue):
# position in matrix
self.x = x
self.y = y
# keeps track of which walls are still visible
self.walls = [True, True, True, True]
# checks if cell has been visited during generation
self.generated = False
# checks if cell is on path during solving
self.on_path = False
# checks if cell has been visited during solving
self.visited = False
self.cell_size = cell_size
self.screen = screen
self.black = black
self.white = white
self.red = red
self.blue = blue
def draw_cell(self):
# coordinates on screen
x = self.x * self.cell_size
y = self.y * self.cell_size
# draws a wall if it still exists
if self.walls[0]:
pygame.draw.line(self.screen, self.black, (x, y), (x + self.cell_size, y), 5)
if self.walls[1]:
pygame.draw.line(self.screen, self.black,
(x, y + self.cell_size), (x + self.cell_size, y + self.cell_size), 5)
if self.walls[2]:
pygame.draw.line(self.screen, self.black,
(x + self.cell_size, y), (x + self.cell_size, y + self.cell_size), 5)
if self.walls[3]:
pygame.draw.line(self.screen, self.black, (x, y), (x, y + self.cell_size), 5)
# marks out white if generated during generation
if self.generated:
pygame.draw.rect(self.screen, self.white, (x, y, self.cell_size, self.cell_size))
class Maze:
def __init__(self, screen, cell_size, rows, cols, white, black, red, blue):
self.screen = screen
self.cell_size = cell_size
self.rows = rows
self.cols = cols
self.state = None
self.maze = []
self.stack = []
self.current_x = 0
self.current_y = 0
self.row = []
self.neighbours = []
self.black = black
self.white = white
self.red = red
self.blue = blue
self.cell = None
def on_start(self):
# maintains the current state
# maze matrix of cell instances
self.maze = []
# stack of current cells on path
self.stack = []
self.current_x, self.current_y = 0, 0
self.maze.clear()
self.stack.clear()
for x in range(self.cols):
self.row = []
for y in range(self.rows):
self.cell = Cell(x, y, self.cell_size, self.screen, self.black, self.white, self.red, self.blue)
self.row.append(self.cell)
self.maze.append(self.row)
def in_bounds(self, x, y):
return 0 <= x < self.cols and 0 <= y < self.rows
def find_next_cell(self, x, y):
# keeps track of valid neighbors
self.neighbours = []
# loop through these two arrays to find all 4 neighbor cells
dx, dy = [1, -1, 0, 0], [0, 0, 1, -1]
for d in range(4):
# add cell to neighbor list if it is in bounds and not generated
if self.in_bounds(x + dx[d], y + dy[d]):
if not self.maze[x + dx[d]][y + dy[d]].generated:
self.neighbours.append((x + dx[d], y + dy[d]))
# returns a random cell in the neighbors list, or -1 -1 otherwise
if len(self.neighbours) > 0:
return self.neighbours[random.randint(0, len(self.neighbours) - 1)]
else:
return -1, -1
def remove_wall(self, x1, y1, x2, y2):
# x distance between original cell and neighbor cell
xd = self.maze[x1][y1].x - self.maze[x2][y2].x
# to the bottom
if xd == 1:
self.maze[x1][y1].walls[3] = False
self.maze[x2][y2].walls[1] = False
# to the top
elif xd == -1:
self.maze[x1][y1].walls[1] = False
self.maze[x2][y2].walls[3] = False
# y distance between original cell and neighbor cell
xy = self.maze[x1][y1].y - self.maze[x2][y2].y
# to the right
if xy == 1:
self.maze[x1][y1].walls[0] = False
self.maze[x2][y2].walls[2] = False
# to the left
elif xy == -1:
self.maze[x1][y1].walls[2] = False
self.maze[x2][y2].walls[0] = False
def create_maze(self):
self.maze[self.current_x][self.current_y].generated = True
# self.maze[self.current_x][self.current_y].draw_current()
next_cell = self.find_next_cell(self.current_x, self.current_y)
# checks if a neighbor was returned
if next_cell[0] >= 0 and next_cell[1] >= 0:
self.stack.append((self.current_x, self.current_y))
self.remove_wall(self.current_x, self.current_y, next_cell[0], next_cell[1])
self.current_x = next_cell[0]
self.current_y = next_cell[1]
# no neighbor, so go to the previous cell in the stack
elif len(self.stack) > 0:
previous = self.stack.pop()
self.current_x = previous[0]
self.current_y = previous[1]
def main():
WIDTH, HEIGHT = 800, 800
CELL_SIZE = 40
ROWS, COLUMNS = int(HEIGHT / CELL_SIZE), int(WIDTH / CELL_SIZE)
# color variables
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
RED = (255, 0, 0)
BLUE = (0, 0, 255)
# initialize pygame
pygame.init()
SCREEN = pygame.display.set_mode((WIDTH, HEIGHT))
SCREEN.fill(WHITE)
pygame.display.set_caption("Maze Gen")
CLOCK = pygame.time.Clock()
FPS = 60
m = Maze(SCREEN, CELL_SIZE, ROWS, COLUMNS, WHITE, BLACK, RED, BLUE)
m.on_start()
running = True
while running:
CLOCK.tick(FPS)
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
for i in range(m.cols):
for j in range(m.rows):
m.maze[i][j].draw_cell()
m.create_maze()
pygame.display.flip()
if __name__ == "__main__":
main()
pygame.quit()
Call m.create_maze() in a loop before the application loop. Terminate the loop when len(m.stack) == 0:
def main():
# [...]
m = Maze(SCREEN, CELL_SIZE, ROWS, COLUMNS, WHITE, BLACK, RED, BLUE)
m.on_start()
while True:
m.create_maze()
if len(m.stack) == 0:
break
running = True
while running:
CLOCK.tick(FPS)
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
for i in range(m.cols):
for j in range(m.rows):
m.maze[i][j].draw_cell()
pygame.display.flip()

Sliding image puzzle game - image not centered when game window is resized

I am working on a sliding puzzle game, but when I adjust the game window of the game the image with tiles is stock on the left side and it cannot go with center. I borrowed this code from GitHub. I am new with python and starting to explore new things. I want to learn more in python and thank you in advance. These are the codes:
import pygame as pg
import os.path
import random
import sys
class PuzzlerGame():
def init(self):
global BASICFONT
window_width = 1380
# window_width =700
window_height = 770
self.tile_width = 150
# self.tile_width = 75
self.tile_height = 150
# self.tile_height = 75
self.coloumn = 4
self.rows = 4
self.img_list = [0, "image1.jpg", "image2.jpg", "image3.jpg", "image4.jpg",
"image5.jpg", "image6.jpg", "image7.jpg", "image8.jpg",
"image9.jpg", "image10.jpg", ]
self.empty_tile = (3, 3)
global emptyc, emptyr
emptyc, emptyr = 3, 3
self.color = (255, 130, 130)
# white = (215,215,215)
self.yellow = (255, 255, 0)
self.red = (200, 15, 15)
self.black = (0, 0, 0)
self.tiles = {}
pg.init()
self.gameWindow = pg.display.set_mode((window_width, window_height))
# pg.display.set_caption("Puzzler")
pg.display.set_caption("Fun City slide puzzle")
# self.gameWindow.fill(white)
self.gameWindow.fill(self.red)
pg.display.update()
if (os.path.isfile('level.txt')):
lfile = open('level.txt', 'r')
# print(storefile)
self.level = int(lfile.read())
# self.level=str(lfile.read())
# print(self.highscore)
lfile.close()
else:
self.level = 1
# self.intro()
self.start(1)
def message(self, v1, u1, text):
rect_w = 70
rect_h = 70
font = pg.font.SysFont('comicsansms', 25)
TextSurf = font.render(text, True, self.black)
TextRect = TextSurf.get_rect()
TextRect.center = ((v1 * rect_w + ((rect_w - 3) / 2)),
(u1 * rect_h + (rect_h / 2)))
self.gameWindow.blit(TextSurf, TextRect)
pg.display.update()
def buttons(self, text):
# rect_w = 70
rect_w = 180
# rect_h = 70
rect_h = 180
color = self.color
# additional button
mouse_pos = pg.mouse.get_pos()
click = pg.mouse.get_pressed()
if (self.v * rect_w + rect_w - 3 > mouse_pos[0] > self.v * rect_w
and self.u * rect_h + rect_h - 3 > mouse_pos[1] > self.u * rect_h):
if int(text) <= self.level:
# if str(text)<=self.level:
color = (255, 30, 30)
if click[0] == 1:
self.start(int(text))
else:
pass
pg.draw.rect(self.gameWindow, color, [self.v * rect_w, self.u * rect_h,
rect_w - 100, rect_h - 3])
self.message(self.v, self.u, text)
# self.message(text)
pg.display.update()
def intro(self):
# additional for button
# NEW_SURF, NEW_RECT = self.makeText("New game", self.yellow, window_width - 120, window_height - 90)
while True:
self.v = 4
self.u = 5
for event in pg.event.get():
if event.type == pg.QUIT:
pg.quit()
sys.exit()
for rec in range(1, 2): # Level Number showing
# self.labels(300, 430, "Tap to Start", (0, 0, 255))
# self.buttons(str(rec))
# self.labels()
# self.message(self.v,self.u,str(rec))
self.v += 1
if self.v == 8:
self.v = 4
self.u += 1
#############################################################################
def labels(self, v1, u1, text, color, size=20):
font = pg.font.SysFont('comicsansms', size)
TextSurf = font.render(text, True, color)
TextRect = TextSurf.get_rect()
# print(TextRect)
TextRect.center = (v1, u1)
self.gameWindow.blit(TextSurf, TextRect)
pg.display.update()
def check(self):
global game_over
j, k = 0, 0
tag_list = []
for i in range(1, 17):
# print("checking ",i,tiles[(j,k)])
tag = "tag" + str(i)
# print(tag,j,k)
if self.tiles[(j, k)][1] == tag:
tag_list.append(tag)
j += 1
if j > 3:
k += 1
j = 0
else:
break
if i == 16:
print("GAME FINISHED")
game_over = True
def shift(self, c, r):
global emptyc, emptyr
rect_color = (255, 255, 255) # the square for suffling
# rect_color = (0,0,0)
self.gameWindow.blit(
self.tiles[(c, r)][0],
(emptyc * self.tile_width, emptyr * self.tile_height))
'''pg.draw.rect(gameWindow,black,[c*tile_width,r*tile_height,
tile_width-1,tile_height-1])'''
self.gameWindow.blit(
self.tiles[self.empty_tile][0],
(c * self.tile_width, r * self.tile_height))
# state[(emptyc, emptyr)] = state[(c, r)]
# state[(c, r)] = empty_tile
temp = self.tiles[(c, r)]
# print(temp,c,r)
self.tiles[(c, r)] = self.tiles[(emptyc, emptyr)]
self.tiles[(emptyc, emptyr)] = temp
emptyc, emptyr = c, r
# tiles[(emptyc, emptyr)].fill(black)
pg.draw.rect(self.gameWindow, rect_color, [c * self.tile_width, r * self.tile_height,
self.tile_width - 1, self.tile_height - 1])
self.empty_tile = (emptyc, emptyr)
# empty_tile.fill(0,0,0)
pg.display.flip()
def shuffle(self):
global emptyc, emptyr
# keep track of last shuffling direction to avoid "undo" shuffle moves
last_r = 0
for i in range(100):
# slow down shuffling for visual effect
pg.time.delay(50)
while True:
# pick a random direction and make a shuffling move
# if that is possible in that direction
r = random.randint(1, 4)
if (last_r + r == 5):
# don't undo the last shuffling move
continue
if r == 1 and (emptyc > 0):
self.shift(emptyc - 1, emptyr) # shift left
elif r == 4 and (emptyc < self.coloumn - 1):
self.shift(emptyc + 1, emptyr) # shift right
elif r == 2 and (emptyr > 0):
self.shift(emptyc, emptyr - 1) # shift up
elif r == 3 and (emptyr < self.rows - 1):
self.shift(emptyc, emptyr + 1) # shift down
else:
# the random shuffle move didn't fit in that direction
continue
last_r = r
break # a shuffling move was made
def start(self, l):
f = 1
imageX = 350
imageY = 50
global level, game_over
game_over = False
level = l
img = self.img_list[level]
self.image = pg.image.load("./Res/" + img)
button = pg.image.load("./efx/" + "button.jpg")
self.button = pg.image.load("./efx/" + "button.jpg")
self.gameWindow.fill((190, 190, 190)) # color of the window
for r in range(self.coloumn):
for c in range(self.rows):
tag = "tag" + str(f)
tile = self.image.subsurface(c * self.tile_width, r * self.tile_height,
self.tile_width - 1, self.tile_height - 1)
f += 1
self.tiles[(c, r)] = (tile, tag)
if (c, r) == self.empty_tile:
pg.draw.rect(self.gameWindow, (255, 255, 255),
# pg.draw.rect(self.gameWindow,(260,260,260),
[c * self.tile_width, r * self.tile_height,
self.tile_width - 1, self.tile_height - 1])#width and height of the white tile
break
self.gameWindow.blit(tile, (c * self.tile_width, r * self.tile_height)) # uploading the image through the window
#self.gameWindow.blit(tile,(imageX,imageY))
pg.display.update()
# print(tile)
# print(tiles)
# text = "Level "+str(level)
text = "Have fun!"
self.labels(350, 625, text, (0, 0, 255))
# self.labels(300,625,"Click to start Game",(0,0,255))
self.labels(700, 300, "Tap to start Game", (0, 0, 255))
self.gameWindow.blit(button, (640, 180))
pg.display.update()
self.gameloop()
def gameloop(self):
started = False
show_sol = False
global level
# self.gameWindow.fill((190,190,190),(150,610,300,40))
while True:
if game_over:
self.labels(300, 300, "Good job well played", (255, 100, 30), 50)
# self.labels(300,625,"Click to next Level",(0,0,255))
for event in pg.event.get():
# print(event)
if event.type == pg.QUIT:
pg.quit()
sys.exit()
if event.type == pg.MOUSEBUTTONDOWN:
# print(event.type)
# print(event.dict)
# shuffle()
if not started:
self.shuffle()
self.gameWindow.fill((190, 190, 190), (150, 610, 300, 40))
# self.labels(300,625,"Right click to see Solution",(0,0,255))
started = True
if game_over:
level += 1
# self.labels(300,300,"Good job well played",(255,100,30),50)
# self.labels(300,625,"Click to next Level",(0,0,255))
if self.level < level:
self.level += 1
file = open("level.txt", "w")
file.write(str(self.level))
file.close()
self.start(level)
if event.dict['button'] == 1:
mouse_pos = pg.mouse.get_pos()
c = mouse_pos[0] // self.tile_width
r = mouse_pos[1] // self.tile_height
# print("dot posn",emptyc,emptyr)
# print("mouse posn",c,r)
if c == emptyc and r == emptyr:
continue
elif c == emptyc and (r == emptyr - 1 or r == emptyr + 1):
self.shift(c, r)
self.check()
elif r == emptyr and (c == emptyc - 1 or c == emptyc + 1):
self.shift(c, r)
self.check()
# print(c,r)
elif event.dict['button'] == 3:
saved_image = self.gameWindow.copy()
# print(saved_image)
# gameWindow.fill(255,255,255)
self.gameWindow.blit(self.image, (0, 0))
pg.display.flip()
show_sol = True
elif show_sol and (event.type == pg.MOUSEBUTTONUP):
# stop showing the solution
self.gameWindow.blit(saved_image, (0, 0))
pg.display.flip()
show_sol = False
if name == "main":
PuzzlerGame()
The dest argument of pygame.Surface.blit() can also be a rectangle. To center an image on the screen get the bounding rectangle of the image with pygame.Surface.get_rect. Set the center of the rectangle by the center of the screen. Use the rectangle to blit the image:
game_window_rect = self.gameWindow.get_rect()
dest_rect = saved_image.get_rect(center = game_window_rect.center)
self.gameWindow.blit(saved_image, dest_rect)

Pathfinding Visualizer in pygame [duplicate]

This question already has answers here:
tips on Adding/creating a drop down selection box in pygame
(1 answer)
trying creating dropdown menu pygame, but got stuck
(1 answer)
Closed 1 year ago.
I am still learning how to code and wanted to create a pathfinding visualizer by following this video:
https://www.youtube.com/watch?v=JtiK0DOeI4A&ab_channel=TechWithTim
I think i understand the algorithm and the whole code more or less, but i want to expand the program. I want to implement more algorithms, some buttons and a file dialog to import and export labyrinths and stop the time the algorithm takes and some stuff like that. I know how to implement another algorithm, but i am new to Pygame, so i have no idea how to display that. I tried to combined it with PyQt5 and searched for different solutions with Pygame itself, but nothing really worked.
How can i add some bar or something like that above where i can choose the algorithm i want to use with a dropdown menu or similar and import the labyrinth and all that stuff?
This is the corresponding code:
import pygame
import math
from queue import PriorityQueue
RED = (255, 0, 0)
GREEN = (0, 255, 0)
BLUE = (0, 0, 255)
YELLOW = (255, 255, 0)
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
PURPLE = (128, 0, 128)
ORANGE = (255, 165, 0)
GREY = (128, 128, 128)
TURQUOISE = (64, 224, 208)
class Spot:
def __init__(self, row, col, width, total_rows):
self.row = row
self.col = col
self.x = row * width
self.y = col * width
self.color = WHITE
self.neighbors = []
self.width = width
self.total_rows = total_rows
def get_pos(self):
return (self.row, self.col)
def is_closed(self):
return (self.color == RED)
def is_open(self):
return (self.color == GREEN)
def is_barrier(self):
return (self.color == BLACK)
def is_start(self):
return (self.color == ORANGE)
def is_end(self):
return (self.color == TURQUOISE)
def reset(self):
self.color = WHITE
def make_closed(self):
self.color = RED
def make_open(self):
self.color = GREEN
def make_barrier(self):
self.color = BLACK
def make_start(self):
self.color = ORANGE
def make_end(self):
self.color = TURQUOISE
def make_path(self):
self.color = PURPLE
def draw(self, win):
pygame.draw.rect(win, self.color, (self.x, self.y, self.width, self.width))
def update_neighbors(self, grid):
self.neightbors = []
if self.row < self.total_rows -1 and not grid[self.row + 1][self.col].is_barrier(): # DOWN
self.neightbors.append(grid[self.row + 1][self.col])
if self.row > 0 and not grid[self.row - 1][self.col].is_barrier(): # UP
self.neightbors.append(grid[self.row - 1][self.col])
if self.col < self.total_rows -1 and not grid[self.row][self.col + 1].is_barrier(): # RIGHT
self.neightbors.append(grid[self.row][self.col + 1])
if self.col > 0 and not grid[self.row][self.col - 1].is_barrier(): # LEFT
self.neightbors.append(grid[self.row][self.col - 1])
def __lt__(self, other):
return (False)
def h(p1, p2):
x1, y1 = p1
x2, y2 = p2
return (abs(x1 - x2) + abs(y1 - y2))
def reconstruct_path(came_from, current, draw):
while current in came_from:
current = came_from[current]
current.make_path()
draw()
def algorithm(draw, grid, start, end):
count = 0
open_set = PriorityQueue()
open_set.put((0, count, start))
came_from = {}
g_score = {spot: float ("inf") for row in grid for spot in row}
g_score[start] = 0
f_score = {spot: float ("inf") for row in grid for spot in row}
f_score[start] = h(start.get_pos(), end.get_pos())
open_set_hash = {start}
while not open_set.empty():
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
current = open_set.get()[2]
open_set_hash.remove(current)
if current == end: # draws the path
reconstruct_path(came_from, end, draw)
end.make_end()
return True
for neighbor in current.neightbors:
temp_g_score = g_score[current] + 1
if temp_g_score < g_score[neighbor]:
came_from[neighbor] = current
g_score[neighbor] = temp_g_score
f_score[neighbor] = temp_g_score + h(neighbor.get_pos(), end.get_pos())
if neighbor not in open_set_hash:
count += 1
open_set.put((f_score[neighbor], count, neighbor))
open_set_hash.add(neighbor)
neighbor.make_open()
draw()
if current != start:
current.make_closed()
return False
def make_grid(rows, width):
grid = []
gap = width // rows
for i in range(rows):
grid.append([])
for j in range(rows):
spot = Spot(i, j, gap, rows)
grid[i].append(spot)
return grid
def draw_grid(win, rows, width):
gap = width // rows
for i in range(rows):
pygame.draw.line(win, GREY, (0, i * gap), (width, i * gap))
for j in range(rows):
pygame.draw.line(win, GREY, (j * gap, 0), (j * gap, width))
def draw(win, grid, rows, width):
win.fill(WHITE)
for row in grid:
for spot in row:
spot.draw(win)
draw_grid(win, rows, width)
pygame.display.update()
def get_clicked_pos(pos, rows, width):
gap = width // rows
y, x = pos
row = y // gap
col = x // gap
return (row, col)
def main():
width = 800
win = pygame.display.set_mode((width, width))
pygame.display.set_caption("A* Path Finding Algorithm")
ROWS = 50
grid = make_grid(ROWS, width)
start = None
end = None
run = True
while run:
draw(win, grid, ROWS, width)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
if pygame.mouse.get_pressed()[0]:
pos = pygame.mouse.get_pos()
row, col = get_clicked_pos(pos, ROWS, width)
spot = grid [row][col]
if not start and spot != end:
start = spot
start.make_start()
elif not end and spot != start:
end = spot
end.make_end()
elif spot != end and spot != start:
spot.make_barrier()
elif pygame.mouse.get_pressed()[2]:
pos = pygame.mouse.get_pos()
row, col = get_clicked_pos(pos, ROWS, width)
spot = grid [row][col]
spot.reset()
if spot == start:
start = None
elif spot == end:
end = None
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_SPACE and start and end:
for row in grid:
for spot in row:
spot.update_neighbors(grid)
algorithm(lambda: draw(win, grid, ROWS, width), grid, start, end)
if event.key == pygame.K_c:
start = None
end = None
grid = make_grid(ROWS, width)
pygame.quit()
if __name__ == "__main__":
main()

a*star pathfinding visualization is incredibly slow / python

I tried to implement shortest pathfinding algorithm using python and pygame. The algorithm and visualization seems to work. However, the time it takes to find the path is increasing exponentially as I put the start and end nodes far away from each other. It takes hours to find a path with a length of 5 or 6 nodes. I reckon the problem is with one of the loops in the astar function but I don't know which one and how to solve it. I searched the internet regarding the problem but couldn't find any solution. Here is the code:
import pygame
class cube():
rows = 30
w = 840
def __init__(self, pos=None, color=None):
self.pos = pos
self.color = color
self.neighbours = []
def draw(self, surface):
dis = self.w // self.rows
i = self.pos[0]
j = self.pos[1]
pygame.draw.rect(surface, self.color, (i * dis + 1, j * dis + 1, dis - 2, dis - 2))
class node(cube):
def __init__(self, pos=None, color=None, parent=None):
super().__init__(pos, color)
self.parent = parent
self.g = 0
self.h = 0
self.f = self.g + self.h
def __eq__(self, other):
return self.pos == other.pos
//here is the astar function
def astar(start, end):
global rows, path, obs_list, start_node, end_node, window, closed_list, blue, children, red, green, grey
counter = 0
green = (0,255,0)
red = (255,0,0)
blue = (0,0,255)
grey = (170,170,170)
start_node = node(start, red)
end_node = node(end, red)
start_node.g = start_node.h = start_node.f = 0
end_node.g = end_node.h = end_node.f = 0
open_list = []
closed_list = []
open_list.append(start_node)
while len(open_list) > 0:
# Get the current node
current_node = open_list[0]
current_index = 0
for index, item in enumerate(open_list):
if item.f < current_node.f:
current_node = item
current_index = index
# Pop current off open list, add to closed list
open_list.pop(current_index)
closed_list.append(current_node)
visualize(window, open_list)
if current_node.pos == end_node.pos:
path = []
current = current_node
while current is not None:
path.append(current.pos)
current = current.parent
return path[::-1] # Return reversed path
children = []
for new_position in [(0, -1), (0, 1), (-1, 0), (1, 0), (-1, -1), (-1, 1), (1, -1), (1, 1)]:
node_position = (current_node.pos[0] + new_position[0], current_node.pos[1] + new_position[1])
#check if neighbour isn't a obstacle
if node_position in obs_list:
# print("not possible")
continue
#check if neighbour isn't in closed list
#create a new node
new_node = node(node_position, green, current_node)
# new node is the child of the previous node. Add child node to the list
children.append(new_node)
# print(children)
#loop through children
for child in children:
for closed_child in closed_list:
if child == closed_child:
continue
child.g = current_node.g + 10
child.h = ((child.pos[0] - end_node.pos[0]) **2) + ((child.pos[1] - end_node.pos[1]) **2)
child.f = child.g + child.h
for open_node in open_list:
if child == open_node and child.g > open_node.g:
continue
# visualize(window, children)
# Add the child to the open list
open_list.append(child)
def draw_grid(w, rows, window):
size_between = w // rows
x = 0
y = 0
for l in range(rows):
x = x + size_between
y = y + size_between
pygame.draw.line(window, (255,255,255), (x, 0), (x, w))
pygame.draw.line(window, (255,255,255), (0,y), (w,y))
def visualize(surface, list):
global red, blue, green, grey, counter
for i,c in enumerate(list):
c.draw(surface)
pygame.display.update()
def show_path(surface):
global path
for i in path:
node_path = cube(i, color=(0,0,255))
node_path.draw(surface)
pygame.display.update()
for c in (path):
node_p = cube(c, color=(35, 180, 89))
node_p.draw(surface)
def draw_inital(surface):
surface.fill((0, 0, 0))
draw_grid(840, 30, surface)
start_node.draw(surface)
end_node.draw(surface)
pygame.display.update()
def mouse_press(x):
global rows, window, obs, start_node, end_node, obs_list
t = x[0]
w = x[1]
g1 = t // (840 // rows)
g2 = w // (840 // rows)
obs = cube((g1,g2), (125, 125, 125))
if obs.pos == start_node.pos:
obs.color = (255,0,0)
obs.draw(window)
pygame.display.update()
elif obs.pos == end_node.pos:
obs.color = (255, 0, 0)
obs.draw(window)
pygame.display.update()
else:
if obs.pos not in obs_list:
obs_list.append(obs.pos)
obs.draw(window)
pygame.display.update()
def main():
global start_node, end_node, rows, window, counter, obs_list, start, end
width = 840
rows = 30
window = pygame.display.set_mode((width,width))
start = (12, 24)
end = (12, 26)
obs_list = []
start_node = node(start, color=(255,0,0))
end_node = node(end, color=(255,0,0))
start_node.draw(window)
end_node.draw(window)
draw_inital(window)
loop = True
while loop:
ev = pygame.event.get()
for event in ev:
if event.type == pygame.QUIT:
pygame.quit()
if pygame.mouse.get_pressed()[0]:
try:
pos = pygame.mouse.get_pos()
mouse_press(pos)
except AttributeError:
pass
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_SPACE:
loop = False
break
path = astar(start, end)
print(path)
show_path(window)
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
main()
Your code is very hard to read. Here's some observations.
Minor Bug
child.g = current_node.g + 10
Needs to become
child.g = current_node.g + 1
This fixes the problem, but only by luck. It is not actually the solution!
Real Bug
You are adding too many elements to the open_list.
for new_position in ...:
# create a new node
new_node = node(node_position, green, current_node)
...
children.append(new_node)
# print(children)
# loop through children
for child in children:
...
open_list.append(child)
Notice that for each neighbor (for new_position in ...) (8x) you are appending a
new child to the children list, then iterating over all children so far. That's 1/2 * 8 * 8 = 32 children added.
A simple fix is to have only one loop per neighbor/child, not the two that you currently have.
Priority Queue
You are not using a Priority Queue for the open list. This means for each element that you pop from the open list, you are scanning through the whole list.
Use this: https://docs.python.org/3/library/heapq.html
Set Membership
for closed_child in closed_list:
if child == closed_child:
continue
Should become, but would require making the 'node' type hashable:
closed_list = set()
...
if child in closed_list:
continue
Heuristic Function
Bug: you forgot the square root when computing the Euclidean distance.

Putting the scrolling camera in a mini-window in pygame

I'm trying to create a game where the action is shown in a little box within the main screen object, freeing up the surrounding space for text and menus and what-not. Since the map is larger than the allotted window, I coded a basic "camera" that follows the player around. It mostly works, but I'm having trouble "trimming off" the area outside of this window.
Here's the relevant bits of code (EDITED to provide Working Example):
import pygame, os, sys
from pygame.locals import *
pygame.init()
RIGHT = 'RIGHT'
LEFT = 'LEFT'
UP = 'UP'
DOWN = 'DOWN'
class Camera():
def __init__(self, screen, x_ratio = 1, y_ratio = 1, x_offset = 0, y_offset = 0):
self.screen = screen.copy()
self.rec = self.screen.get_rect()
self.rec.width *= x_ratio
self.rec.height *= y_ratio
self.x_offset = x_offset
self.y_offset = y_offset
def get_pos(self):
return (self.x_offset - self.rec.x, self.y_offset - self.rec.y)
def get_window(self):
w = pygame.Rect(self.rec)
w.topleft = (0 - self.rec.x, 0 - self.rec.y)
return w
def move(self, x, y):
"""Move camera into new position"""
self.rec.x = x
self.rec.y = y
def track(self, obj):
while obj.rec.left < self.rec.left:
self.rec.x -= 1
while obj.rec.right > self.rec.right:
self.rec.x += 1
while obj.rec.top < self.rec.top:
self.rec.y -= 1
while obj.rec.bottom > self.rec.bottom:
self.rec.y += 1
class Map:
def __init__(self, width, height):
self.width = width
self.height = height
self.rec = pygame.Rect(0,0,self.width,self.height)
def draw(self, screen):
pygame.draw.rect(screen, (200,200,200), self.rec)
class Obj:
def __init__(self, char, x = 0, y = 0, width = 0, height = 0):
self.width = width
self.height = height
self.rec = pygame.Rect(x, y, width, height)
self.cur_map = None
self.timers = {}
#Dummying in chars for sprites
self.char = char
self.x_dir = 1
self.y_dir = 1
self.speed = 1
self.moving = False
def move(self):
if self.x_dir != 0 or self.y_dir != 0:
new_x = self.rec.x + (self.x_dir*self.speed)
new_y = self.rec.y + (self.y_dir*self.speed)
new_rec = pygame.Rect(new_x, new_y, self.width, self.height)
#Keep movement within bounds of map
while new_rec.left < self.cur_map.rec.left:
new_rec.x += 1
while new_rec.right > self.cur_map.rec.right:
new_rec.x -= 1
while new_rec.top < self.cur_map.rec.top:
new_rec.y += 1
while new_rec.bottom > self.cur_map.rec.bottom:
new_rec.y -= 1
self.rec = new_rec
def set_dir(self, d):
self.x_dir = 0
self.y_dir = 0
if d == LEFT:
self.x_dir = -1
elif d == RIGHT:
self.x_dir = 1
elif d == UP:
self.y_dir = -1
elif d == DOWN:
self.y_dir = 1
def set_moving(self, val = True):
self.moving = val
class Game:
def __init__(self):
self.screen_size = (800, 600)
self.screen = pygame.display.set_mode(self.screen_size)
self.map_screen = self.screen.copy()
self.title = 'RPG'
pygame.display.set_caption(self.title)
self.camera = Camera(self.screen, 0.75, 0.75)#, 10, 75)
self.fps = 80
self.clock = pygame.time.Clock()
self.debug = False
self.bg_color = (255,255,255)
self.text_size = 18
self.text_font = 'Arial'
self.text_style = pygame.font.SysFont(self.text_font, self.text_size)
self.key_binds = {LEFT : [K_LEFT, K_a], RIGHT : [K_RIGHT, K_d], UP : [K_UP, K_w], DOWN : [K_DOWN, K_s],
'interact' : [K_RETURN, K_z], 'inventory' : [K_i, K_SPACE], 'quit' : [K_ESCAPE]}
self.player = Obj('p', 0, 0, 10, self.text_size)
def draw(self, obj):
char = obj.char
self.draw_text(char, obj.rec.x, obj.rec.y, screen = self.map_screen)
def draw_text(self, text, x, y, color = (0,0,0), screen = None):
textobj = self.text_style.render(text, 1, color)
textrect = textobj.get_rect()
textrect.x = x
textrect.y = y
if screen == None:
"""Use default screen"""
self.screen.blit(textobj, textrect)
else:
screen.blit(textobj, textrect)
def play(self):
done = False
cur_map = Map(800, 800)
self.map_screen = pygame.Surface((cur_map.width, cur_map.height))
self.map_screen.fill(self.bg_color)
bg = pygame.Surface((cur_map.width, cur_map.height))
cur_map.draw(bg)
self.player.cur_map = cur_map
while not done:
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
sys.exit()
if event.type == KEYDOWN:
if event.key in self.key_binds[LEFT]:
self.player.set_dir(LEFT)
self.player.set_moving()
elif event.key in self.key_binds[RIGHT]:
self.player.set_dir(RIGHT)
self.player.set_moving()
elif event.key in self.key_binds[UP]:
self.player.set_dir(UP)
self.player.set_moving()
elif event.key in self.key_binds[DOWN]:
self.player.set_dir(DOWN)
self.player.set_moving()
elif event.type == KEYUP:
self.player.set_moving(False)
if self.player.moving:
self.player.move()
self.camera.track(self.player)
self.clock.tick()
self.screen.fill(self.bg_color)
self.map_screen.blit(bg, (0,0))
self.draw(self.player)
pygame.draw.rect(self.map_screen, (0,0,0), self.camera.rec, 1)
#self.screen.blit(self.map_screen, (0,0), [0 - self.camera.rec.x, 0 - self.camera.rec.y, self.camera.rec.width, self.camera.rec.height])
self.screen.blit(self.map_screen, self.camera.get_pos(), self.camera.get_window())
pygame.display.flip()
game = Game()
game.play()
Moving the player past past the bounds of the camera's window causes the window to roll up completely and disappear. I tried adjusting the blitting coordinates, as advised earlier, but it seems to only change the direction in which the window rolls up.
From your updated code, the blitting coordinates for self.screen.blit(...) are still changing: self.camera.get_window() changes value because rec.x and rec.y are values referring to the player position within the map. Hence you should define a constant minimap coordinate, this should be the same as the camera offset.
self.screen.blit(self.map_screen, (self.camera.x_offset,self.camera.y_offset), (*self.camera.get_pos(), self.camera.rec.width, self.camera.rec.height))
Change the Camera().get_pos() to:
def get_pos(self):
return (self.rec.x, self.rec.y)
I believe I only changed the self.screen.blit(...) and stopped using or rewrote your Camera functions as you're confusing yourself with all the rec variables.
To illustrate it working amend the Map().draw(screen) to:
def draw(self, screen):
pygame.draw.rect(screen, (200,200,200), self.rec)
pygame.draw.circle(screen, (255, 255, 255), (50, 50), 20, 2)
One tip as well don't draw the entire map at each loop, just the part that will be visible.

Categories