astar imlpementation

This commit is contained in:
Maciej 2023-05-14 09:10:31 +02:00
commit 423e7e858c
33 changed files with 994 additions and 0 deletions

7
.gitignore vendored Normal file
View File

@ -0,0 +1,7 @@
/venv
.DS_Store
/.vscode
__pycache__
#PyCharm
.idea/

View File

@ -0,0 +1,80 @@
from domain.commands.vacuum_move_command import VacuumMoveCommand
from domain.world import World
class State:
def __init__(self, x, y):
self.x = x
self.y = y
def __hash__(self):
return hash((self.x, self.y))
def __eq__(self, other):
return self.x == other.x and self.y == other.y
class GoAnyDirectionBFS:
def __init__(self, world: World, start_state: State, goal_state: State):
self.start_state = start_state
self.goal_state = goal_state
self.visited = set()
self.parent = {}
self.actions = []
self.path = []
self.world = world
self.queue = []
def search(self):
self.queue.append(self.start_state)
self.visited.add(self.start_state)
while self.queue:
state = self.queue.pop(0)
if state == self.goal_state:
self.actions = self.get_actions()
self.path = self.get_path()
return True
for successor in self.successors(state):
if successor not in self.visited:
self.visited.add(successor)
self.parent[successor] = state
self.queue.append(successor)
return False
def successors(self, state):
new_successors = [
State(state.x + dx, state.y + dy)
for dx, dy in [(1, 0), (0, 1), (-1, 0), (0, -1)]
if self.world.accepted_move(state.x + dx, state.y + dy)
]
return new_successors
def get_actions(self):
actions = []
state = self.goal_state
while state != self.start_state:
parent_state = self.parent[state]
dx = state.x - parent_state.x
dy = state.y - parent_state.y
if dx == 1:
actions.append("RIGHT")
elif dx == -1:
actions.append("LEFT")
elif dy == 1:
actions.append("DOWN")
elif dy == -1:
actions.append("UP")
state = parent_state
actions.reverse()
return actions
def get_path(self):
path = []
state = self.goal_state
while state != self.start_state:
path.append((state.x, state.y))
state = self.parent[state]
path.append((self.start_state.x, self.start_state.y))
path.reverse()
return path

View File

@ -0,0 +1,113 @@
import heapq
from domain.world import World
class State:
def __init__(self, x, y, direction=(1, 0), entity=None):
self.x = x
self.y = y
self.direction = direction
def __hash__(self):
return hash((self.x, self.y))
def __eq__(self, other):
return (
self.x == other.x
and self.y == other.y
and self.direction == other.direction
)
def heuristic(self, goal_state):
return abs(self.x - goal_state.x) + abs(self.y - goal_state.y)
class Node:
def __init__(self, state: State, g_score: int, goal_state: State):
self.state = state
self.g_score = g_score
self.f_score = g_score + state.heuristic(goal_state)
self.parent = None
self.action = None
def __lt__(self, other):
return self.f_score < other.f_score
def action_sequence(node: Node):
actions = []
while node.parent:
actions.append(node.action)
node = node.parent
actions.reverse()
return actions
class RotateAndGoAStar:
def __init__(self, world: World, start_state: State, goal_state: State):
self.world = world
self.start_state = start_state
self.goal_state = goal_state
self.fringe = []
self.enqueued_states = set()
self.explored = set()
self.actions = []
def get_g_score(self, state):
return self.world.get_cost(state.x, state.y)
def search(self):
heapq.heappush(
self.fringe, Node(self.start_state, 0, self.goal_state)
)
while self.fringe:
elem = heapq.heappop(self.fringe)
if self.is_goal(elem.state):
self.actions = action_sequence(elem)
return True
self.explored.add(elem.state)
for action, state in self.successors(elem.state):
if state in self.explored:
continue
new_g_score = new_g_score = elem.g_score + self.world.get_cost(state.x, state.y)
if state not in self.enqueued_states:
next_node = Node(state, new_g_score, self.goal_state)
next_node.action = action
next_node.parent = elem
heapq.heappush(self.fringe, next_node)
self.enqueued_states.add(state)
elif new_g_score < self.get_g_score(state):
for node in self.fringe:
if node.state == state:
node.g_score = new_g_score
node.f_score = (
new_g_score + node.state.heuristic(self.goal_state)
)
node.parent = elem
node.action = action
heapq.heapify(self.fringe)
break
return False
def successors(self, state: State):
new_successors = [
("RR", State(state.x, state.y, (-state.direction[1], state.direction[0]))),
("RL", State(state.x, state.y, (state.direction[1], -state.direction[0]))),
]
next_x = state.x + state.direction[0]
next_y = state.y + state.direction[1]
if self.world.accepted_move(next_x, next_y):
new_successors.append(
("GO", State(next_x, next_y, state.direction))
)
return new_successors
def is_goal(self, state: State) -> bool:
return (
state.x == self.goal_state.x
and state.y == self.goal_state.y )

View File

@ -0,0 +1,83 @@
import queue
from domain.world import World
class State:
def __init__(self, x, y, direction=(1, 0)):
self.x = x
self.y = y
self.direction = direction
def __hash__(self):
return hash((self.x, self.y))
def __eq__(self, other):
return (self.x == other.x and self.y == other.y
and self.direction == other.direction)
class Node:
def __init__(self, state: State):
self.state = state
self.parent = None
self.action = None
def action_sequence(node: Node):
actions = []
while node.parent:
actions.append(node.action)
node = node.parent
actions.reverse()
return actions
class RotateAndGoBFS:
def __init__(self, world: World, start_state: State, goal_state: State):
self.world = world
self.start_state = start_state
self.goal_state = goal_state
self.fringe = queue.Queue()
self.enqueued_states = set()
self.explored = set()
self.actions = []
def search(self):
self.fringe.put(Node(self.start_state))
while self.fringe:
elem = self.fringe.get()
if self.is_goal(elem.state):
self.actions = action_sequence(elem)
return True
self.explored.add(elem.state)
for (action, state) in self.successors(elem.state):
if state in self.explored or state in self.enqueued_states:
continue
next_node = Node(state)
next_node.action = action
next_node.parent = elem
self.fringe.put(next_node)
self.enqueued_states.add(state)
return False
def successors(self, state: State):
new_successors = [
# rotate right
("RR", State(state.x, state.y, (-state.direction[1], state.direction[0]))),
# rotate left
("RL", State(state.x, state.y, (state.direction[1], -state.direction[0]))),
]
if self.world.accepted_move(state.x + state.direction[0], state.y + state.direction[1]):
new_successors.append(
("GO", State(state.x + state.direction[0], state.y + state.direction[1], state.direction)))
return new_successors
def is_goal(self, state: State) -> bool:
return (
state.x == self.goal_state.x
and state.y == self.goal_state.y
)

58
Project discription.txt Normal file
View File

@ -0,0 +1,58 @@
******
Dokumentacja projektu "Automatyczny robot sprzątający"
Wprowadzenie:
Projekt "Automatyczny robot sprzątający" jest projektem bazującym się na symulacji pracy robota sprzątającego w pomieszczeniu za pomocą sztucznej inteligencji. Robot ma za zadanie wyznaczać miejsca do sprzątania oraz uniknąć przeszkód oraz reagować na zdarzenia losowe. Projekt jest napisany w języku Python.
Instrukcja obsługi:
Uruchomienie projektu:
Aby uruchomić projekt należy uruchomić plik "main.py" za pomocą interpretera Python. Projektu wyświetli się w konsoli.Po uruchomieniu projektu na ekranie wyświetli się plansza o wymiarach NxN (default: 10x10). Robot "Cleaner" (oznaczony jako "R" na planszy) startuje z pozycji (0,0). użytkownik ma za zadanie wprowadzić pozycje do sprzątania, które są oznaczone na planszy jako litery "D". Możliwe pozycje to liczby od 0 do N-1.
Użytkownik wprowadza pozycje za pomocą terminala. Wprowadzenie koordynat odbywa się w następujący sposób:
Najpierw wprowadzamy numer wiersza, a następnie numer kolumny, oddzielając je spacją.
Przykładowo, jeśli chcemy wskazać pozycję (4,5) wpisujemy: "4 5".
Po wskazaniu pozycji do sprzątania, użytkownik musi uniknąć przeszkód, które są oznaczone na planszy jako znak "X". Robot nie może przejść przez przeszkody. Jeśli użytkownik wskazuje pozycję przeszkody, projektu zwróci błąd i będzie wymagała podania nowych współrzędnych.
Przebieg projektu:
Robot, zgodnie z zbudowaną mapą, musi obliczyć najkrótszą ścieżkę do sprzątania wszystkich pozycji oraz uniknąć przeszkód. Podczas sprzątania mogą wystąpić przypadkowe zdarzenia, na które robot będzie reagował. W tym celu, z pomocą sieci neuronowych, robot analizuje zdjęcie zdarzenia, aby wybrać najlepsze rozwiązania.
Zakończenie projektu:
Program kończy swoje działanie w momencie, gdy robot posprząta wszystkie przez użytkownika wybrane pola do sprzątania. Na zakończenie programu zostanie wyświetlona liczba wykonanych ruchów przez robota oraz podjęte decyzje w przypadku zaistnienia zdarzeń.
Możliwe modyfikacje:
Projekt zostanie napisany z myślą o możliwości łatwej modyfikacji. Można zmienić wymiary planszy, dodać lub usunąć przeszkody oraz ilość przypadkowych zdarzeń i pozycji do sprzątania. Wszystkie te zmiany można wprowadzić w pliku "config.py".
Podsumowanie:
Projekt "Automatyczny robot sprzątający" to prosty, ale edukacyjny projekt programistyczny. Użytkownik ma za zadanie wskazanie pozycji, które robot powinien posprzątać, a także koordynat przeszkody. Natomiast zadaniem robota, który został zbudowany przy użyciu sztucznej inteligencji, jest unikanie przeszkód, podejmowanie decyzji w przypadku wystąpienia przypadkowych zdarzeń oraz sprzątanie wyznaczonych punktów. Projekt został napisany w języku Python z wykorzystaniem sztucznej inteligencji. Analiza zdjęć jest oparta na sieciach neuronowych.
******
Documentation of the "Automatic Cleaning Robot" project
Introduction:
The "Automatic Cleaning Robot" project is based on simulating the work of a cleaning robot in a room using artificial intelligence. The robot is tasked with determining the areas to be cleaned, avoiding obstacles, and reacting to random events. The project is written in Python.
User Guide:
Starting the project:
To start the project, you need to run the "main.py" file using a Python interpreter. The project will be displayed on the console. Once the project is launched, a 10x10 board will be displayed on the screen. The "Cleaner" robot (marked as "R" on the board) starts from the position (0,0). The user needs to enter the positions to be cleaned, which are marked as the letter "D" on the board. The possible positions are numbers from 0 to 9.
The user enters the positions through the terminal. The entry of coordinates is done as follows:
First, we enter the row number, and then the column number, separating them with a space.
For example, if we want to indicate the position (4,5), we enter "4 5".
After indicating the positions to be cleaned, the user must avoid obstacles, which are marked on the board as the "X" symbol. The robot cannot pass through obstacles. If the user points to an obstacle position, the project will return an error and require new coordinates.
Project process:
Based on the built map, the robot must calculate the shortest path to clean all positions and avoid obstacles. Random events may occur during cleaning, to which the robot will react. To do this, with the help of neural networks, the robot analyzes the image of the event to choose the best solutions.
Project conclusion:
The program is ending when the robot cleans all the fields selected by the user. At the end of the program, the number of robot moves performed and the decisions made in case of events will be displayed.
Possible modifications:
The "Automatic cleaning robot" project has been designed with the possibility of easy modifications in mind. Users can change the dimensions of the board, add or remove obstacles, and adjust the number of random events and cleaning positions. All these changes can be made in the "config.py" file.
Summary:
The "Automatic cleaning robot" project is a simple yet educational programming project. Users are tasked with specifying the positions that the robot should clean, as well as the coordinates of obstacles. The robot, built using artificial intelligence, is responsible for avoiding obstacles, making decisions in case of random events, and cleaning the designated points. The project was written in Python with the use of artificial intelligence. The analysis of images is based on neural networks.
******

58
README.md Normal file
View File

@ -0,0 +1,58 @@
******
Dokumentacja projektu "Automatyczny robot sprzątający"
Wprowadzenie:
Projekt "Automatyczny robot sprzątający" jest projektem bazującym się na symulacji pracy robota sprzątającego w pomieszczeniu za pomocą sztucznej intelegencji. Robot ma za zadanie wyznaczać miejsca do sprzątania oraz uniknąć przeszkód oraz reagować na zdarzenia randomowe. Projekt jest napisany w języku Python.
Instrukcja obsługi:
Uruchomienie projektu:
Aby uruchomić projekt należy uruchomić plik "main.py" za pomocą interpretera Python. Projektu wyświetli się w konsoli.Po uruchomieniu projektu na ekranie wyświetli się plansza o wymiarach 10x10. Robot "Cleaner" (oznaczony jako "R" na planszy) startuje z pozycji (0,0). użytkownik ma za zadanie wprowadzić pozycje do sprzątania, które są oznaczone na planszy jako litery "D". Możliwe pozycje to liczby od 0 do 9.
Użytkownik wprowadza pozycje za pomocą terminala. Wprowadzenie koordynat odbywa się w następujący sposób:
Najpierw wprowadzamy numer wiersza, a następnie numer kolumny, oddzielając je spacją.
Przykładowo, jeśli chcemy wskazać pozycję (4,5) wpisujemy: "4 5".
Po wskazaniu pozycji do sprzątania, użytkownik musi uniknąć przeszkód, które są oznaczone na planszy jako znak "X". Robot nie może przejść przez przeszkody. Jeśli użytkownik wskazuje pozycję przeszkody, projektu zwróci błąd i będzie wymagała podania nowych koordynatów.
Przebieg projektu:
Robot, zgodnie z zbudowaną mapą, musi obliczyć najkrótszą ścieżkę do sprzątania wszystkich pozycji oraz uniknąć przeszkód. Podczas sprzątania mogą wystąpić przypadkowe zdarzenia, na które robot będzie reagował. W tym celu, z pomocą sieci neuronowych, robot analizuje zdjęcie zdarzenia, aby wybrać najlepsze rozwiązania.
Zakończenie projektu:
Program kończy swoje działanie w momencie, gdy robot posprząta wszystkie przez użytkownika wybrane pola do sprzątania. Na zakończenie programu zostanie wyświetlona liczba wykonanych ruchów przez robota oraz podjęte decyzje w przypadku zaistnienia zdarzeń.
Możliwe modyfikacje:
Projekt zostanie napisany z myślą o możliwości łatwej modyfikacji. Można zmienić wymiary planszy, dodać lub usunąć przeszkody oraz ilość przypadkowych zdarzeń i pozycji do sprzątania. Wszystkie te zmiany można wprowadzić w pliku "config.py".
Podsumowanie:
Projekt "Automatyczny robot sprzątający" to prosty, ale edukacyjny projekt programistyczny. Użytkownik ma za zadanie wskazanie pozycji, które robot powinien posprzątać, a także koordynat przeszkody. Natomiast zadaniem robota, który został zbudowany przy użyciu sztucznej inteligencji, jest unikanie przeszkód, podejmowanie decyzji w przypadku wystąpienia przypadkowych zdarzeń oraz sprzątanie wyznaczonych punktów. Projekt został napisany w języku Python z wykorzystaniem sztucznej inteligencji.Analiza zdięć jest oparta na sieciach neuronowych.
******
Documentation of the "Automatic Cleaning Robot" project
Introduction:
The "Automatic Cleaning Robot" project is based on simulating the work of a cleaning robot in a room using artificial intelligence. The robot is tasked with determining the areas to be cleaned, avoiding obstacles, and reacting to random events. The project is written in Python.
User Guide:
Starting the project:
To start the project, you need to run the "main.py" file using a Python interpreter. The project will be displayed on the console. Once the project is launched, a 10x10 board will be displayed on the screen. The "Cleaner" robot (marked as "R" on the board) starts from the position (0,0). The user needs to enter the positions to be cleaned, which are marked as the letter "D" on the board. The possible positions are numbers from 0 to 9.
The user enters the positions through the terminal. The entry of coordinates is done as follows:
First, we enter the row number, and then the column number, separating them with a space.
For example, if we want to indicate the position (4,5), we enter "4 5".
After indicating the positions to be cleaned, the user must avoid obstacles, which are marked on the board as the "X" symbol. The robot cannot pass through obstacles. If the user points to an obstacle position, the project will return an error and require new coordinates.
Project process:
Based on the built map, the robot must calculate the shortest path to clean all positions and avoid obstacles. Random events may occur during cleaning, to which the robot will react. To do this, with the help of neural networks, the robot analyzes the image of the event to choose the best solutions.
Project conclusion:
The program is ending when the robot cleans all the fields selected by the user. At the end of the program, the number of robot moves performed and the decisions made in case of events will be displayed.
Possible modifications:
The "Automatic cleaning robot" project has been designed with the possibility of easy modifications in mind. Users can change the dimensions of the board, add or remove obstacles, and adjust the number of random events and cleaning positions. All these changes can be made in the "config.py" file.
Summary:
The "Automatic cleaning robot" project is a simple yet educational programming project. Users are tasked with specifying the positions that the robot should clean, as well as the coordinates of obstacles. The robot, built using artificial intelligence, is responsible for avoiding obstacles, making decisions in case of random events, and cleaning the designated points. The project was written in Python with the use of artificial intelligence. The analysis of images is based on neural networks.
******

4
config.ini Normal file
View File

@ -0,0 +1,4 @@
[APP]
cat = False
movement = robot
#accept: human, robot

View File

@ -0,0 +1,3 @@
class Command:
def run(self):
raise NotImplementedError()

View File

@ -0,0 +1,70 @@
from random import randint
from typing import Tuple
import pygame
from domain.commands.command import Command
from domain.entities.cat import Cat
from domain.world import World
class RandomCatMoveCommand(Command):
def __init__(self, world: World, cat: Cat) -> None:
super().__init__()
self.world = world
self.cat = cat
def run(self):
move_vector = (0, 0)
now = pygame.time.get_ticks()
# region cat random movement
cat = self.world.cat
if now - cat.last_tick >= cat.cooldown:
if not cat.busy:
while True:
cat.direction = randint(0, 3)
if not (
(cat.direction == 0 and cat.y == 0)
or (cat.direction == 1 and cat.x == self.world.width - 1)
or (cat.direction == 2 and cat.y == self.world.height - 1)
or (cat.direction == 3 and cat.x == 0)
):
break
if cat.direction == 0: # up
if cat.busy:
move_vector = (0, -1)
cat.busy = not cat.busy
if cat.direction == 1: # right
if cat.busy:
move_vector = (1, 0)
cat.busy = not cat.busy
if cat.direction == 2: # down
if cat.busy:
move_vector = (0, 1)
cat.busy = not cat.busy
if cat.direction == 3: # left
if cat.busy:
move_vector = (-1, 0)
cat.busy = not cat.busy
cat.last_tick = pygame.time.get_ticks()
if move_vector == (0, 0):
return
end_x = cat.x + move_vector[0]
end_y = cat.y + move_vector[1]
if (
end_x > self.world.width - 1
or end_y > self.world.height - 1
or end_x < 0
or end_y < 0
):
return
self.world.obstacles[cat.x][cat.y].remove(cat)
cat.x = end_x
cat.y = end_y
self.world.obstacles[end_x][end_y].append(cat)
# endregion cat random movement

View File

@ -0,0 +1,33 @@
from typing import Tuple
from domain.commands.command import Command
from domain.entities.vacuum import Vacuum
from domain.world import World
class VacuumMoveCommand(Command):
def __init__(
self, world: World, vacuum: Vacuum, move_vector: Tuple[int, int]
) -> None:
super().__init__()
self.world = world
self.vacuum = vacuum
self.dx = move_vector[0]
self.dy = move_vector[1]
def run(self):
end_x = self.vacuum.x + self.dx
end_y = self.vacuum.y + self.dy
if not self.world.accepted_move(end_x, end_y):
return
if self.world.is_garbage_at(end_x, end_y):
if self.vacuum.get_container_filling() < 100:
self.vacuum.increase_container_filling()
self.world.dust[end_x][end_y].pop()
if self.world.is_docking_station_at(end_x, end_y):
self.vacuum.dump_trash()
self.vacuum.x = end_x
self.vacuum.y = end_y

15
domain/entities/cat.py Normal file
View File

@ -0,0 +1,15 @@
import pygame
from domain.entities.entity import Entity
from domain.world import World
class Cat(Entity):
def __init__(self, x: int, y: int):
super().__init__(x, y, "CAT")
self.last_tick = pygame.time.get_ticks()
self.cooldown = 1000
self.velocity = 1
self.busy = False
self.sleeping = False
self.direction = 0

View File

@ -0,0 +1,10 @@
from domain.entities.entity import Entity
from domain.world import World
class Doc_Station(Entity):
def __init__(self, x: int, y: int):
super().__init__(x, y, "DOC_STATION")
self.power = True
# TODO Docing Station: add more properties

View File

@ -0,0 +1,5 @@
class Entity:
def __init__(self, x: int, y: int, type: str):
self.x = x
self.y = y
self.type = type

View File

@ -0,0 +1,11 @@
from domain.entities.entity import Entity
from domain.world import World
class Garbage(Entity):
def __init__(self, x: int, y: int):
super().__init__(x, y, "GARBAGE")
self.wet = False
self.size = 0
# TODO GARBAGE: add more properties

10
domain/entities/plant.py Normal file
View File

@ -0,0 +1,10 @@
from domain.entities.entity import Entity
from domain.world import World
class Plant(Entity):
def __init__(self, x: int, y: int):
super().__init__(x, y, "PLANT")
self.watered = 100
# TODO PLANT: add more properties to

22
domain/entities/vacuum.py Normal file
View File

@ -0,0 +1,22 @@
from domain.entities.entity import Entity
from domain.world import World
class Vacuum(Entity):
def __init__(self, x: int, y: int):
super().__init__(x, y, "VACUUM")
self.direction = (1, 0)
self.battery = 100
self.cleaning_detergent = 100
self.container_filling = 0
def increase_container_filling(self) -> None:
self.container_filling += 25
def dump_trash(self) -> None:
self.container_filling = 0
def get_container_filling(self):
return self.container_filling
# TODO VACUUM: add more properties

52
domain/world.py Normal file
View File

@ -0,0 +1,52 @@
from domain.entities.entity import Entity
class World:
def __init__(self, width: int, height: int) -> object:
self.costs = [[1000 for j in range(height)] for i in range(width)]
self.width = width
self.height = height
self.dust = [[[] for j in range(height)] for i in range(width)]
self.obstacles = [[[] for j in range(height)] for i in range(width)]
self.vacuum = None
self.cat = None
self.doc_station = None
def add_entity(self, entity: Entity):
if entity.type == "PEEL":
self.dust[entity.x][entity.y].append(entity)
elif entity.type == "VACUUM":
self.vacuum = entity
elif entity.type == "DOC_STATION":
self.doc_station = entity
elif entity.type == "CAT":
self.cat = entity
self.obstacles[entity.x][entity.y].append(entity)
else:
self.obstacles[entity.x][entity.y].append(entity)
def is_obstacle_at(self, x: int, y: int) -> bool:
return bool(self.obstacles[x][y])
def is_garbage_at(self, x: int, y: int) -> bool:
return bool(self.dust[x][y])
def is_docking_station_at(self, x: int, y: int) -> bool:
return bool(self.doc_station.x == x and self.doc_station.y == y)
def accepted_move(self, checking_x, checking_y):
if (
checking_x > self.width - 1
or checking_y > self.height - 1
or checking_x < 0
or checking_y < 0
):
return False
if self.is_obstacle_at(checking_x, checking_y):
return False
return True
def get_cost(self, x, y):
return self.costs[x][y]

166
main.py Normal file
View File

@ -0,0 +1,166 @@
from random import randint
import pygame
import configparser
from domain.commands.random_cat_move_command import RandomCatMoveCommand
from domain.commands.vacuum_move_command import VacuumMoveCommand
from domain.entities.cat import Cat
from domain.entities.entity import Entity
from domain.entities.vacuum import Vacuum
from domain.entities.docking_station import Doc_Station
from domain.world import World
from view.renderer import Renderer
# from AI_brain.movement import GoAnyDirectionBFS, State
# from AI_brain.rotate_and_go_bfs import RotateAndGoBFS, State
from AI_brain.rotate_and_go_astar import RotateAndGoAStar, State
config = configparser.ConfigParser()
config.read("config.ini")
class Main:
def __init__(self):
tiles_x = 10
tiles_y = 10
self.renderer = Renderer(800, 800, tiles_x, tiles_y)
self.world = generate_world(tiles_x, tiles_y)
self.commands = []
self.clock = pygame.time.Clock()
self.running = True
self.fps = 60
def run(self):
while self.running:
self.process_input()
self.update()
self.renderer.render(self.world)
self.clock.tick(self.fps)
pygame.quit()
def run_robot(self):
self.renderer.render(self.world)
start_state = State(self.world.vacuum.x, self.world.vacuum.y)
end_state = State(self.world.doc_station.x, self.world.doc_station.y)
# path_searcher = GoAnyDirectionBFS(self.world, start_state, end_state)
# path_searcher = RotateAndGoBFS(self.world, start_state, end_state)
path_searcher = RotateAndGoAStar(self.world, start_state, end_state)
if not path_searcher.search():
print("No solution")
exit(0)
path_searcher.actions.reverse()
while self.running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
self.running = False
if len(path_searcher.actions) > 0:
action_direction = path_searcher.actions.pop()
# self.handle_action1(action_direction)
self.handle_action2(action_direction)
self.update()
self.renderer.render(self.world)
self.clock.tick(5)
pygame.quit()
def handle_action1(self, action):
if action == "UP":
self.commands.append(
VacuumMoveCommand(self.world, self.world.vacuum, (0, -1))
)
elif action == "DOWN":
self.commands.append(
VacuumMoveCommand(self.world, self.world.vacuum, (0, 1))
)
elif action == "LEFT":
self.commands.append(
VacuumMoveCommand(self.world, self.world.vacuum, (-1, 0))
)
elif action == "RIGHT":
self.commands.append(
VacuumMoveCommand(self.world, self.world.vacuum, (1, 0))
)
def handle_action2(self, action):
if action == "GO":
self.commands.append(
VacuumMoveCommand(self.world, self.world.vacuum, self.world.vacuum.direction)
)
elif action == "RR":
self.world.vacuum.direction = (-self.world.vacuum.direction[1], self.world.vacuum.direction[0])
elif action == "RL":
self.world.vacuum.direction = (self.world.vacuum.direction[1], -self.world.vacuum.direction[0])
def process_input(self):
for event in pygame.event.get():
if event.type == pygame.QUIT:
self.running = False
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_LEFT:
self.commands.append(
VacuumMoveCommand(self.world, self.world.vacuum, (-1, 0))
)
if event.key == pygame.K_RIGHT:
self.commands.append(
VacuumMoveCommand(self.world, self.world.vacuum, (1, 0))
)
if event.key == pygame.K_UP:
self.commands.append(
VacuumMoveCommand(self.world, self.world.vacuum, (0, -1))
)
if event.key == pygame.K_DOWN:
self.commands.append(
VacuumMoveCommand(self.world, self.world.vacuum, (0, 1))
)
def update(self):
if config.getboolean("APP", "cat"):
self.commands.append(RandomCatMoveCommand(self.world, self.world.cat))
for command in self.commands:
command.run()
self.commands.clear()
def generate_world(tiles_x: int, tiles_y: int) -> World:
world = World(tiles_x, tiles_y)
for _ in range(35):
temp_x = randint(0, tiles_x - 1)
temp_y = randint(0, tiles_y - 1)
world.add_entity(Entity(temp_x, temp_y, "PEEL"))
world.vacuum = Vacuum(0, 0)
world.doc_station = Doc_Station(9, 8)
if config.getboolean("APP", "cat"):
world.cat = Cat(7, 8)
world.add_entity(world.cat)
world.add_entity(Entity(2, 8, "PLANT1"))
world.add_entity(Entity(4, 1, "PLANT1"))
world.add_entity(Entity(3, 4, "PLANT2"))
world.add_entity(Entity(8, 8, "PLANT2"))
world.add_entity(Entity(9, 3, "PLANT3"))
world.add_entity(Entity(4, 0, "PLANT2"))
for x in range(world.width):
for y in range(world.height):
if world.is_garbage_at(x, y):
world.costs[x][y] = 1
else:
world.costs[x][y] = 1000
return world
if __name__ == "__main__":
app = Main()
if config["APP"]["movement"] == "human":
app.run()
elif config["APP"]["movement"] == "robot":
app.run_robot()

Binary file not shown.

After

Width:  |  Height:  |  Size: 621 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 765 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 411 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 584 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

BIN
media/sprites/peel.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

BIN
media/sprites/tile.jpeg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 112 KiB

BIN
media/sprites/vacuum.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 122 KiB

BIN
media/sprites/wall.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

3
requirements.txt Normal file
View File

@ -0,0 +1,3 @@
pygame
configparser
formaFormatting: Provider - black

191
view/renderer.py Normal file
View File

@ -0,0 +1,191 @@
import random
from random import randint
import pygame
import configparser
from pygame import Color
from domain.entities.cat import Cat
from domain.entities.entity import Entity
from domain.world import World
config = configparser.ConfigParser()
config.read("config.ini")
class Renderer:
def __init__(
self,
width=800,
height=800,
tiles_x=10,
tiles_y=10,
):
self.width = width
self.height = height
self.tiles_x = tiles_x
self.tiles_y = tiles_y
self.tile_width = self.width / self.tiles_x
self.tile_height = self.height / self.tiles_y
pygame.init()
pygame.display.set_caption("AI Vacuum Cleaner")
self.screen = pygame.display.set_mode((self.width, self.height))
self.font = pygame.font.SysFont("Arial", 26, bold=True)
self.sprites = {
"VACUUM": pygame.transform.scale(
pygame.image.load("media/sprites/vacuum.png"),
(self.tile_width, self.tile_height),
),
"DOC_STATION": pygame.transform.scale(
pygame.image.load("media/sprites/docking_station.png"),
(self.tile_width, self.tile_height),
),
"WALL": pygame.transform.scale(
pygame.image.load("media/sprites/wall.png"),
(self.tile_width, self.tile_height),
),
"TILE": pygame.transform.scale(
pygame.image.load("media/sprites/tile_cropped.jpeg"),
(self.tile_width, self.tile_height),
),
"PEEL": pygame.transform.scale(
pygame.image.load("media/sprites/peel.webp"),
(self.tile_width, self.tile_height),
),
"CAT_FRONT": pygame.transform.scale(
pygame.image.load("media/sprites/cat/standing_front.png"),
(self.tile_width, self.tile_height),
),
"CAT_BACK": pygame.transform.scale(
pygame.image.load("media/sprites/cat/standing_back.png"),
(self.tile_width, self.tile_height),
),
"CAT_LEFT": pygame.transform.scale(
pygame.image.load("media/sprites/cat/standing_left.png"),
(self.tile_width, self.tile_height),
),
"CAT_RIGHT": pygame.transform.scale(
pygame.image.load("media/sprites/cat/standing_right.png"),
(self.tile_width, self.tile_height),
),
"PLANT1": pygame.transform.scale(
pygame.image.load("media/sprites/plants/plant1.png"),
(
self.tile_width + self.tile_width / 4,
self.tile_height + self.tile_height / 4,
),
),
"PLANT2": pygame.transform.scale(
pygame.image.load("media/sprites/plants/plant2.png"),
(
self.tile_width + self.tile_width / 4,
self.tile_height + self.tile_height / 4,
),
),
"PLANT3": pygame.transform.scale(
pygame.image.load("media/sprites/plants/plant3.png"),
(
self.tile_width + self.tile_width / 4,
self.tile_height + self.tile_height / 4,
),
),
}
self.cat_direction_sprite = {
0: self.sprites["CAT_BACK"],
1: self.sprites["CAT_RIGHT"],
2: self.sprites["CAT_FRONT"],
3: self.sprites["CAT_LEFT"],
}
def render(self, world: World):
self.render_floor()
self.render_board()
for x in range(world.width):
for y in range(world.height):
for entity in world.dust[x][y]:
self.draw_entity(entity)
for x in range(world.width):
for y in range(world.height):
for entity in world.obstacles[x][y]:
self.draw_entity(entity)
self.draw_entity(world.vacuum)
self.draw_entity(world.doc_station)
if config.getboolean("APP", "cat"):
self.draw_entity(world.cat)
pygame.display.update()
def line(self, x_1, y_1, x_2, y_2, color=None):
pygame.draw.line(self.screen, color, (x_1, y_1), (x_2, y_2))
def render_board(self, color=Color("black")):
for i in range(1, self.tiles_x):
self.line(
self.tile_width * i, 0, self.tile_width * i, self.height, color=color
)
for i in range(1, self.tiles_y):
self.line(
0, self.tile_height * i, self.width, self.tile_height * i, color=color
)
def draw_entity(self, entity: Entity):
sprite = self.sprites.get(entity.type, None)
draw_pos = (entity.x * self.tile_width, entity.y * self.tile_height)
if "PEEL" in entity.type:
draw_pos = (
(entity.x - 0.1) * self.tile_width,
(entity.y - 0.25) * self.tile_height,
)
if "PLANT" in entity.type:
draw_pos = (
(entity.x - 0.1) * self.tile_width,
(entity.y - 0.25) * self.tile_height,
)
if "CAT" in entity.type and isinstance(entity, Cat):
sprite = self.cat_direction_sprite[entity.direction]
if "VACUUM" in entity.type:
# Add text displaying container filling level
text_surface = self.font.render(
f"Filling: {entity.container_filling}%", True, Color("black")
)
text_pos = (
draw_pos[0] + self.tile_width / 2 - text_surface.get_width() / 2,
draw_pos[1] + self.tile_height,
)
self.screen.blit(text_surface, text_pos)
sprite = self.create_vacuum_sprite(entity)
if "DOC_STATION" in entity.type:
draw_pos = (
(entity.x - 0.1) * self.tile_width,
(entity.y - 0.25) * self.tile_height,
)
self.screen.blit(sprite, draw_pos)
def create_vacuum_sprite(self, vacuum):
angles = {
(1, 0): 0,
(-1, 0): 180,
(0, 1): 270,
(0, -1): 90,
}
init_sprite = self.sprites.get(vacuum.type, None)
return pygame.transform.rotate(init_sprite, angles[vacuum.direction])
def draw_sprite(self, x: int, y: int, sprite_name: str):
self.screen.blit(
self.sprites[sprite_name], (x * self.tile_width, y * self.tile_height)
)
def fill_grid_with_sprite(self, sprite):
for tile_x in range(self.tiles_x):
for tile_y in range(self.tiles_y):
self.draw_sprite(tile_x, tile_y, sprite)
def render_floor(self):
self.fill_grid_with_sprite("TILE")