updated agent navigation

This commit is contained in:
2021-09-15 21:24:34 +01:00
parent acd39d4403
commit 3d36b913b9
3 changed files with 180 additions and 43 deletions

View File

@@ -0,0 +1,73 @@
import random
from dataclasses import dataclass
from maze import Cell
@dataclass
class Choice:
score: int
cell: Cell
def __add__(self, other):
self.score += other
def __sub__(self, other):
self.score -= other
class Agent:
def __init__(self, buffer, grid_size):
self.location = Cell(0, 0)
self.last_visited = Cell(0, 0)
self.buffer = buffer
self.grid_size = grid_size
self.first_step = True
self.auto_finish = 1
def reset(self):
self.location = Cell(0, 0)
self.first_step = True
self.last_visited = Cell(0, 0)
def get_x_position(self):
return (self.location.x * self.grid_size) + self.buffer + self.grid_size / 1.9
def get_y_position(self):
return (self.location.y * self.grid_size) + self.buffer + self.grid_size / 1.9
def move(self, maze):
if self.first_step:
self.first_step = False
return
# Random Walk
# self.location = random.choice(maze.maze[self.location])
# Direct to end
# _, self.location = min(sorted([(maze.distance_map[x], x) for x in maze.maze[self.location]]))
# no backstep.
# self.last_visited = self.location
# Go to end when close.
if maze.distance_map[self.location] <= self.auto_finish:
_, self.location = min(sorted([(maze.distance_map[x], x) for x in maze.maze[self.location]]))
else:
choices = maze.maze[self.location].copy()
try:
choices.remove(self.last_visited)
except ValueError:
pass
if not choices:
self.last_visited = self.location
self.location = maze.maze[self.last_visited][0]
else:
if smart_choices := set(choices).difference(maze.dead_ends):
self.last_visited = self.location
self.location = random.choice(list(smart_choices))
else:
self.last_visited = self.location
self.location = random.choice(choices)

84
game.py
View File

@@ -3,7 +3,8 @@ from itertools import product
import pygame
from pygame.locals import *
from maze import Maze
from maze import Maze, Cell
from agent import Agent
pygame.init()
vec = pygame.math.Vector2 # 2 for two dimensional
@@ -19,22 +20,28 @@ displaysurface = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("Game")
SCREEN_BUFFER = 10
MAZE_SIZE = 15
MAZE_SIZE = 10
GRID_SIZE = 25
class Player(pygame.sprite.Sprite):
def __init__(self):
class VisitedSpot(pygame.sprite.Sprite):
def __init__(self, cell: Cell):
super().__init__()
self.surf = pygame.Surface((30, 30))
self.surf.fill((0, 255, 0))
self.rect = self.surf.get_rect(center=(10, 420))
self.surf = pygame.Surface((GRID_SIZE, GRID_SIZE))
self.surf.fill((0, 122, 000))
self.rect = self.surf.get_rect(topleft=(cell.x * GRID_SIZE + SCREEN_BUFFER, cell.y * GRID_SIZE + SCREEN_BUFFER))
class DeadEndSpot(pygame.sprite.Sprite):
def __init__(self, cell: Cell):
super().__init__()
self.surf = pygame.Surface((GRID_SIZE, GRID_SIZE))
self.surf.fill((122, 0, 000))
self.rect = self.surf.get_rect(topleft=(cell.x * GRID_SIZE + SCREEN_BUFFER, cell.y * GRID_SIZE + SCREEN_BUFFER))
class MazeWall(pygame.sprite.Sprite):
def __init__(self, wall):
wall_width = 2
super().__init__()
wall_width = 2
start_cell, end_cell = wall
x, y = start_cell
vertical = start_cell[1] == end_cell[1]
@@ -48,17 +55,23 @@ class MazeWall(pygame.sprite.Sprite):
else:
self.rect = self.surf.get_rect(midleft=(x * GRID_SIZE + SCREEN_BUFFER, (y + 1) * GRID_SIZE + SCREEN_BUFFER))
agents = list()
agents.append(Agent(SCREEN_BUFFER, GRID_SIZE))
# agents.append(Agent(SCREEN_BUFFER, GRID_SIZE))
# agents.append(Agent(SCREEN_BUFFER, GRID_SIZE))
# agents.append(Agent(SCREEN_BUFFER, GRID_SIZE))
all_sprites = pygame.sprite.Group()
visited_cells_sprites = pygame.sprite.Group()
dead_end_sprites = pygame.sprite.Group()
maze_sprites = pygame.sprite.Group()
maze = Maze(MAZE_SIZE, MAZE_SIZE)
for wall in maze.walls:
wall_sprite = MazeWall(wall)
maze_sprites.add(wall_sprite)
MOVEEVENT = pygame.USEREVENT+1
RESETMAZE = pygame.USEREVENT+2
reset_maze_event = pygame.event.Event(RESETMAZE, message="Reset Maze")
pygame.event.post(reset_maze_event)
pygame.time.set_timer(MOVEEVENT, 1000)
while True:
@@ -66,18 +79,47 @@ while True:
if event.type == QUIT:
pygame.quit()
sys.exit()
if event.type == MOVEEVENT:
for agent in agents:
agent.move(maze)
if agent.location not in maze.visited:
maze.visited.add(agent.location)
visited_cells_sprites.add(VisitedSpot(agent.location))
if agent.location == max(maze.cells):
pygame.event.post(reset_maze_event)
if maze.check_dead_end(agent.location):
dead_end_sprites.add(DeadEndSpot(agent.location))
if event.type == RESETMAZE:
for agent in agents:
agent.reset()
maze = Maze(MAZE_SIZE, MAZE_SIZE)
maze_sprites.empty()
visited_cells_sprites.empty()
dead_end_sprites.empty()
for wall in maze.walls:
wall_sprite = MazeWall(wall)
maze_sprites.add(wall_sprite)
# wipe buffer
displaysurface.fill((0, 0, 0))
# draw border
pygame.draw.rect(displaysurface, (255, 0, 0),
pygame.Rect((SCREEN_BUFFER, SCREEN_BUFFER, MAZE_SIZE * GRID_SIZE, MAZE_SIZE * GRID_SIZE)), 2)
pygame.draw.circle(displaysurface, (0, 255, 0), (25, 25), GRID_SIZE / 3)
# for entity in all_sprites:
# displaysurface.blit(entity.surf, entity.rect)
from maze import Cell
location = Cell(0, 5)
for entity in visited_cells_sprites:
displaysurface.blit(entity.surf, entity.rect)
for entity in dead_end_sprites:
displaysurface.blit(entity.surf, entity.rect)
for agent in agents:
pygame.draw.circle(displaysurface, (0, 255, 0), (agent.get_x_position(), agent.get_y_position()), GRID_SIZE / 3)
for entity in maze_sprites:
displaysurface.blit(entity.surf, entity.rect)
# draw border
pygame.draw.rect(displaysurface, (255, 0, 0),
pygame.Rect((SCREEN_BUFFER, SCREEN_BUFFER, MAZE_SIZE * GRID_SIZE, MAZE_SIZE * GRID_SIZE)), 2)
pygame.display.update()
FramePerSec.tick(FPS)

64
maze.py
View File

@@ -16,16 +16,51 @@ class Wall(NamedTuple):
class Maze:
def __init__(self, width: int, height: int):
self.distance_map = dict()
self.cells = set(Cell(*c) for c in product(range(width), range(height)))
self.maze = dict()
for cell in self.cells:
self.maze[cell] = self.get_neighbours(cell)
self.walls = self.get_walls()
self.generate_maze()
self.remove_blocked_links()
self.build_map()
self.visited = set()
self.dead_ends = dict()
self.dead_end_limit = 5
def get_neighbours(self, cell: Cell) -> Set[Cell]:
def check_dead_end(self, cell: Cell) -> bool:
# return true is new dead end
if cell in self.dead_ends:
return False
if len(self.maze[cell]) == 1:
self.dead_ends[cell] = 0
return True
def build_map(self):
distance = 0
first_cell = max(self.cells)
visited = set()
visited.add(first_cell)
unvisited = self.cells.copy()
unvisited.remove(first_cell)
self.distance_map[first_cell] = 0
current_group = self.maze[first_cell]
while unvisited:
next_group = set()
distance += 1
for node in current_group:
self.distance_map[node] = distance
next_group.update(self.maze[node])
visited.add(node)
unvisited.remove(node)
current_group = next_group.difference(visited)
def get_neighbours(self, cell: Cell) -> List[Cell]:
diffs = ((0, 1), (0, -1), (1, 0), (-1, 0))
return set(Cell(cell.x + x, cell.y + y) for x, y in diffs if Cell(cell.x + x, cell.y + y) in self.cells)
return list(Cell(cell.x + x, cell.y + y) for x, y in diffs if Cell(cell.x + x, cell.y + y) in self.cells)
def get_walls(self) -> Set[Wall]:
walls = set()
@@ -33,6 +68,12 @@ class Maze:
walls.update(set(Wall(*sorted([cell, c])) for c in self.maze[cell]))
return walls
def remove_blocked_links(self):
for wall in self.walls:
cell1, cell2 = wall
self.maze[cell1].remove(cell2)
self.maze[cell2].remove(cell1)
def generate_maze(self) -> None:
visited = set()
unvisited = list(self.cells)
@@ -50,25 +91,6 @@ class Maze:
pass
node = random_neighbour
def generate_prim_maze(self) -> None:
visited = set()
node = random.choice(list(self.cells))
visited.add(node)
removed_walls = set()
wall_list = [w for w in self.walls if node in w]
while wall_list:
random_wall = random.choice(wall_list)
wall_list.remove(random_wall)
cell1, cell2 = random_wall
if (cell1 in visited) != (cell2 in visited):
removed_walls.add(random_wall)
self.walls.remove(random_wall)
visited.add(cell1)
visited.add(cell2)
wall_list.extend(
set(w for w in self.walls if (cell1 in w) or (cell2 in w)).difference(removed_walls)
)
if __name__ == "__main__":
maze = Maze(3, 3)