From 2b96b18acfed7ed4ca00738ab50d02e6f1414c9d Mon Sep 17 00:00:00 2001 From: unknown Date: Wed, 7 Oct 2020 17:56:38 +0530 Subject: [PATCH] Added A* Pathfinder with GUI --- A-Star-GUI/README.md | 18 +++ A-Star-GUI/pathfinding_astar.py | 262 ++++++++++++++++++++++++++++++++ A-Star-GUI/requirements.txt | 1 + 3 files changed, 281 insertions(+) create mode 100644 A-Star-GUI/README.md create mode 100644 A-Star-GUI/pathfinding_astar.py create mode 100644 A-Star-GUI/requirements.txt diff --git a/A-Star-GUI/README.md b/A-Star-GUI/README.md new file mode 100644 index 0000000..e6770db --- /dev/null +++ b/A-Star-GUI/README.md @@ -0,0 +1,18 @@ +# A\* Pathfinding Algorithm 🌟 + +## Implemented with pygame, this script will find the shortest distance between two nodes using A\* Algorithm 🎮 + +## Instructions/ Keys Functionalities : + +- ### Left Click to add start and end nodes +- ### Right Click to remove the nodes +- ### Space Bar to start finding the shortest distance +- ### 'C' to clear and reset the grid + +## Requirements: + + pip install pygame + +![ss](https://user-images.githubusercontent.com/55017730/92324354-88be8200-f05e-11ea-8d10-e8314ec8f5c1.png) + +### By Susnata Goswami(https://github.com/proghead00) diff --git a/A-Star-GUI/pathfinding_astar.py b/A-Star-GUI/pathfinding_astar.py new file mode 100644 index 0000000..3f926af --- /dev/null +++ b/A-Star-GUI/pathfinding_astar.py @@ -0,0 +1,262 @@ +import pygame +import math +from queue import PriorityQueue + +WIDTH = 800 +WIN = pygame.display.set_mode((WIDTH, WIDTH)) # dimension to make it a square +pygame.display.set_caption("A* Path Finding Algorithm") + +RED = (235, 77, 75) +GREEN = (186, 220, 88) +BLUE = (48, 51, 107) +YELLOW = (249, 202, 36) +WHITE = (255, 255, 255) +BLACK = (53, 59, 72) +PURPLE = (130, 88, 159) +ORANGE = (225, 95, 65) +GREY = (128, 128, 128) +TURQUOISE = (10, 189, 227) + + +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_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_neighbors(self, grid): + self.neighbors = [] + # DOWN + if self.row < self.total_rows - 1 and not grid[self.row + 1][self.col].is_barrier(): + self.neighbors.append(grid[self.row + 1][self.col]) + + if self.row > 0 and not grid[self.row - 1][self.col].is_barrier(): # UP + self.neighbors.append(grid[self.row - 1][self.col]) + + # RIGHT + if self.col < self.total_rows - 1 and not grid[self.row][self.col + 1].is_barrier(): + self.neighbors.append(grid[self.row][self.col + 1]) + + if self.col > 0 and not grid[self.row][self.col - 1].is_barrier(): # LEFT + self.neighbors.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) # finding absolute distance + + +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 = {} + # keeps track of current shortest distance from start node to this node + g_score = {spot: float("inf") for row in grid for spot in row} + g_score[start] = 0 + # keeps track of predicted distance from this node to end node + 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: + reconstruct_path(came_from, end, draw) + end.make_end() + return True + + for neighbor in current.neighbors: + 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 # integer division: gap b/w each of these 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)) # horizontal line + for j in range(rows): + pygame.draw.line(win, GREY, (j * gap, 0), + (j * gap, width)) # vertical lines + + +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() + +# getting mouse postiion + + +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 = 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]: # LEFT MOUSE BUTTON: 0 + pos = pygame.mouse.get_pos() + # actual spot in 2D list where mouse is clicked + row, col = get_clicked_pos(pos, ROWS, width) + spot = grid[row][col] + + # if start and end aren't done + if not start and spot != end: + start = spot + start.make_start() + + # to avoid overlapping of start and end node + 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]: # RIGHT MOUSE BUTTON: 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() + + +main(WIN, WIDTH) diff --git a/A-Star-GUI/requirements.txt b/A-Star-GUI/requirements.txt new file mode 100644 index 0000000..231dd17 --- /dev/null +++ b/A-Star-GUI/requirements.txt @@ -0,0 +1 @@ +pygame \ No newline at end of file