diff --git a/agent.py b/agent.py index e69de29..d9e4565 100644 --- a/agent.py +++ b/agent.py @@ -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) + + + diff --git a/game.py b/game.py index 6a9c56f..6cfd43e 100644 --- a/game.py +++ b/game.py @@ -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,24 +20,30 @@ 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 + x, y = start_cell vertical = start_cell[1] == end_cell[1] if vertical: self.surf = pygame.Surface((wall_width, GRID_SIZE)) @@ -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) \ No newline at end of file diff --git a/maze.py b/maze.py index 5439dee..70e0ce7 100644 --- a/maze.py +++ b/maze.py @@ -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)