Compare commits

...

37 Commits
BFS ... master

Author SHA1 Message Date
Konrad Pierzyński
6bbbcbc343 Dodano raporty z metod pythonowych 2019-06-12 10:38:31 +02:00
Konrad Pierzyński
f9a7a69d16 Poprawiono zapis ruchow do pliku 2019-05-22 10:55:13 +02:00
Konrad Pierzyński
6289ce99b1 Usunieto przypadkowego VENV 2019-05-22 10:44:04 +02:00
Konrad Pierzyński
51396183ef Merge branch 'master' of https://git.wmi.amu.edu.pl/s440556/SZI2019SmieciarzWmi 2019-05-22 10:40:57 +02:00
Konrad Pierzyński
e9c93a2b9f Zapis ruchow do pliku 2019-05-22 10:40:30 +02:00
Konrad Pierzyński
2473104b65 Zapis ruchow do pliku 2019-05-22 10:39:56 +02:00
Magdalena Wilczyńska
9567999454 Merge branch 'master' of https://git.wmi.amu.edu.pl/s440556/SZI2019SmieciarzWmi 2019-05-22 10:09:20 +02:00
28d38d7589 windows update 2019-05-21 23:51:03 +02:00
Magdalena Wilczyńska
67e86029bb Merge branch 'master' into map_generator 2019-05-21 22:01:54 +02:00
Magdalena Wilczyńska
06de0e681f Cleaned up a bit 2019-05-21 21:51:10 +02:00
Magdalena Wilczyńska
e9d7f25bbe Added better solution for road modification 2019-05-21 21:24:57 +02:00
Michał Starski
7181bb133a implemented dumping moveset to the file 2019-05-21 11:53:29 +02:00
Magdalena Wilczyńska
0fdb9ce843 Disabled road modification for time being 2019-05-19 18:22:46 +02:00
Magdalena Wilczyńska
b567eec536 Added placing objects 2019-05-19 18:15:25 +02:00
Magdalena Wilczyńska
7664888597 Added saving to file 2019-05-19 17:28:46 +02:00
Magdalena Wilczyńska
36ff47fe3d Added route generating 2019-05-19 17:11:08 +02:00
4c9b2e95c5 Started road generator 2019-05-19 14:42:12 +02:00
b6bcb8be46 Zaktualizuj 'Raports/SI_Raport_3.md' 2019-05-15 08:09:00 +00:00
b999fd4df6 Zaktualizuj 'Raports/SI_Raport_2.md' 2019-05-14 22:18:55 +00:00
c6ab3a51df Zaktualizuj 'Raports/SI_Raport_3.md' 2019-05-14 22:10:25 +00:00
b4c7536e7f Zaktualizuj 'Raports/SI_Raport_3.md' 2019-05-14 22:08:05 +00:00
8890d95a07 Zaktualizuj 'Raports/SI_Raport_3.md' 2019-05-14 22:03:31 +00:00
Magdalena Wilczyńska
9259de3af6 Merge branch 'master' of https://git.wmi.amu.edu.pl/s440556/SZI2019SmieciarzWmi 2019-05-14 23:54:40 +02:00
Magdalena Wilczyńska
d56e137803 Removed old file 2019-05-14 23:54:23 +02:00
bfc31b59f8 Update 'Raports/SI_Raport_3.md' 2019-05-14 21:53:46 +00:00
19e7b98c87 Update 'Raports/SI_Raport_3.md' 2019-05-14 21:52:43 +00:00
Michał Starski
433a8b1ec9 added raport 3 and restored raport 1 2019-05-14 23:50:47 +02:00
Magdalena Wilczyńska
431d130379 Merged with master 2019-05-14 23:45:35 +02:00
Magdalena Wilczyńska
4c598f5af2 Removed debug messages 2019-05-14 23:41:48 +02:00
Magdalena Wilczyńska
f358e3c86a Working version 2019-05-14 23:29:19 +02:00
2ccc2b41b3 Update 'Raports/SI_Raport_2.md' 2019-05-14 21:20:40 +00:00
Magdalena Wilczyńska
b7d463f0ec Added patch to allow app to even start 2019-05-14 21:32:56 +02:00
Magdalena Wilczyńska
265668fa47 Tried really hard to make recursion work 2019-05-14 12:38:49 +02:00
5790a923f0 Why u do da bestfs with bugz 2019-05-13 22:01:33 +02:00
e39b82b4a8 Merged with master 2019-05-13 10:32:03 +02:00
d4cbe19f6e Added missing files 2019-05-13 10:29:34 +02:00
0981dcb8cc Added distance calculating and selecting directions 2019-05-13 10:27:39 +02:00
12 changed files with 698 additions and 19 deletions

2
.gitignore vendored
View File

@ -1,6 +1,8 @@
VENV
WENV
env
**/__pycache__
.vscode
*.swp
linux_env
moveset_data.json

View File

@ -3,10 +3,12 @@ from DataModels.Road import Road
from DataModels.House import House
from DataModels.Dump import Dump
from config import GRID_WIDTH, GRID_HEIGHT, DELAY
from utilities import movement, check_moves
from utilities import movement, check_moves, save_moveset
from Traversal.DFS import DFS
from Traversal.BestFS import BestFS
from Traversal.BFS import BFS
import pygame
class GC(Cell):
moves_made = 0
def __init__(self, x, y, max_rubbish, yellow=0, green=0, blue=0):
@ -35,7 +37,7 @@ class GC(Cell):
item.return_trash(self)
self.update_image()
def get_moves_made(self):
def get_moves_count(self):
return self.moves_made
def find_houses(self,enviromnent, house_count,dump_count, mode):
@ -65,8 +67,43 @@ class GC(Cell):
for x in element_list:
x.unvisited = True
self.moves.reverse()
save_moveset(self.moves)
def find_houses_BestFS(self, environment):
x = self.x
y = self.y
result = [[x,y]]
houses_list = []
dump_list = []
a = 0
for row in environment:
b = 0
for col in row:
if (type(col) is House):
houses_list.append([col,[a,b]])
if (type(col) is Dump):
dump_list.append([col,[a,b]])
b += 1
a += 1
x, y = self.x, self.y
for i in range(len(houses_list)):
available_movement = check_moves(environment, x, y)
output = BestFS(environment, available_movement, [[x,y]], houses_list)
if(output != None):
[x,y],result,houses_list = output[0], output[1], output[2]
self.moves.extend(result[1:])
for i in range(len(dump_list)):
available_movement = check_moves(environment, x, y)
output = BestFS(environment, available_movement, [[x,y]], dump_list)
if(output != None):
[x,y],result,dump_list = output[0], output[1], output[2]
self.moves.extend(result[1:])
self.moves.reverse()
save_moveset(self.moves)
def make_actions_from_list(self,environment):

153
MapGenerator.py Normal file
View File

@ -0,0 +1,153 @@
import random, datetime, itertools
def GenerateMap():
#generate random empty map
width = random.randint(5,15) #up to 15
height = random.randint(5,10) #up to 10
grid = []
row = []
for i in range(width):
row.append('E')
for i in range(height):
grid.append(row.copy())
#define number of roads for each axis
x_roads_count = random.randint(2, max(width//3,2))
y_roads_count = random.randint(2, max(height//3,2))
#select coords of roads for x
x_roads_coordinates = [] #output coords
possible_coordiantes = [i for i in range(width)] #coords to choose from
for i in range(x_roads_count):
coordinate = random.choice(possible_coordiantes)
road_area = [coordinate-1, coordinate, coordinate+1]
possible_coordiantes = [i for i in possible_coordiantes if i not in road_area] #removes road and surrounding coords (total 3 coords) from possible coords
x_roads_coordinates.append(coordinate)
#select coords of roads for y
y_roads_coordinates = [] #output coords
possible_coordiantes = [i for i in range(height)] #coords to choose from
for i in range(y_roads_count):
coordinate = random.choice(possible_coordiantes)
road_area = [coordinate-1, coordinate, coordinate+1]
possible_coordiantes = [i for i in possible_coordiantes if i not in road_area] #removes road and surrounding coords (total 3 coords) from possible coords
y_roads_coordinates.append(coordinate)
#create list of road coordinates
roads = []
for x in x_roads_coordinates:
for y in range(height):
roads.append([x,y])
for y in y_roads_coordinates:
for x in range(width):
roads.append([x,y])
"""AH SHIT HERE WE GO AGAIN"""
#create list of path coords that can become new intersections by removing intersections and 8 adjacent tiles from roads
intersections_area = []
for x in x_roads_coordinates:
for y in y_roads_coordinates:
intersection_area = []
for i in range (-1,2,1):
for j in range (-1,2,1):
intersection_area.append([x+i,y+j])
intersections_area.extend(intersection_area)
possible_roads_to_modify = [i for i in roads if i not in intersections_area]
roads_to_modify = []
for i in range(1,len(possible_roads_to_modify)//3):
choice = random.choice(possible_roads_to_modify)
possible_roads_to_modify.remove(choice)
roads_to_modify.append(choice)
"""CREATION TIME"""
#perform modification based on road
for r in roads_to_modify:
#select possible directions for modification
x, y = r
direction = random.choice([1,-1])
if([x+1, y] in roads or [x-1, y] in roads):
#modify y, as there is road on x coordinates
route = []
current_tile = [x,y+direction]
while(True):
if(current_tile[1]<0 or current_tile[1]>=height or current_tile in roads):
break
else:
route.append(current_tile)
current_tile = [current_tile[0],current_tile[1]+direction]
else:
#modify x, as there is road on y coordinates
route = []
current_tile = [x+direction,y]
while(True):
if(current_tile[0]<0 or current_tile[0]>=width or current_tile in roads):
break
else:
route.append(current_tile)
current_tile = [current_tile[0]+direction,current_tile[1]]
#choose if the route should be complete or not
if(len(route)>1):
if(random.randint(1,100)<=65): #40% chance for route not to be full length
#route.reverse()
route_len = random.randint(1,len(route)-1)
route = route[0:route_len]
#add new route to roads
roads.extend(route)
#insert roads into the grid
for coord in roads:
grid[coord[1]][coord[0]] = "R"
"""OBJECTS BE HERE"""
#Select area that possibly could hold objects
objects_area = []
for r in roads:
objects_area.extend(([r[0]+1,r[1]],[r[0]-1,r[1]],[r[0],r[1]+1],[r[0],r[1]-1]))
objects_area = [c for c in objects_area if c not in roads] #remove coords that contain roads
objects_area.sort()
objects_area = [objects_area[i] for i in range(len(objects_area)) if i == 0 or objects_area[i] != objects_area[i-1]] #remove duplicates
houses_area = [i.copy() for i in objects_area]
for o in objects_area:
if(o[0] < 0 or o[1] < 0 or o[0] >= width or o[1] >= height):
houses_area.remove(o) #remove coords outside borders
#place dumps
dumps_to_place = ["B","Y","G"]
while(len(dumps_to_place) > 0):
dump_coords = random.choice(houses_area)
houses_area.remove(dump_coords)
grid[dump_coords[1]][dump_coords[0]] = dumps_to_place[0]
dumps_to_place.remove(dumps_to_place[0])
#leave random coordinates
houses_to_leave_count = len(houses_area)//4
while(len(houses_area) > houses_to_leave_count):
houses_area.remove(random.choice(houses_area))
#insert houses into the grid
for coord in houses_area:
grid[coord[1]][coord[0]] = "H"
#Select position for GC
GC_position = random.choice(roads)
#Save map to file
name = ".\\Resources\\Maps\\map"+str(datetime.datetime.now().strftime("%Y%m%d%H%M%S"))+"_auto.txt"
map_file = open(name, "w+")
map_file.write(str(width)+" "+str(height)+"\n")
map_file.write(str(GC_position[0])+" "+str(GC_position[1])+"\n")
for row in grid:
map_file.write(" ".join(row)+"\n")
map_file.close()
print(name)
return(name)

138
Raports/SI_Raport_1.md Normal file
View File

@ -0,0 +1,138 @@
# Sztuczna inteligencja 2019 - Raport 1
**Czas trwania opisywanych prac:** 06.03.2018 - 26.03.2018
**Członkowie zespołu:** Anna Nowak, Magdalena Wilczyńska, Konrad Pierzyński, Michał Starski
**Wybrany temat:** Inteligentna śmieciarka
**Link do repozytorium projektu:** https://git.wmi.amu.edu.pl/s440556/SZI2019SmieciarzWmi
## Środowisko agenta i reprezentacja wiedzy
Pracę zaczeliśmy od spisania wszystkiego co będzie nam potrzebne do napisanie działającego środowiska. Notatki
można obejrzeć pod [tym linkiem](https://git.wmi.amu.edu.pl/s440556/SZI2019SmieciarzWmi/src/master/README.md). Pod uwagę wzieliśmy następujące czynniki:
* Jak dana technologia radzi sobie z graficznym przedstawieniem informacji (render mapy i rozmieszczenie na niej obiektów, zmiana stanów obiektów w zależności od sytuacji ...)
* Poziom skomplikowania operacji na strukturach danych
* Podejście języka do paradygmatu obiektowego
* Trudność w implementacji mechanizmów sztucznej inteligencji
* Preferencje programistyczne grupy
Po naradzie i sugestiach od strony prowadzącego zdecydowaliśmy, że do projektu najlepiej będzie pasował język **python** z uwagi na jego uniwersalność i łatwość przetwarzania danych.
### Plansza po której porusza się agent
**Założenia**
1. Plansza jest generowana losowo przy każdym uruchomieniu skryptu
2. Każda plansza zawiera *n* domów i 3 wysypiska śmieci
3. Każdy domek klatkę generuje jeden z 3 rodzajów śmieci z prawdopodobieństwem P (domyślnie 1/25)
4. Agent chodzi po domkach zbiera śmieci i wyrzuca je na odpowiednie wysypisko
5. Agent ma pojemność 10 na każdy rodzaj śmiecia
6. Dom może wygenerować maksymalnie 10 śmieci
7. Plansza jest dyskretna
**Detale implementacyjne**
Środowisko powstało przy użyciu biblioteki **pygame**, która udostępnia nam narzędzia do wygodnego zbudowania schludnie wyglądającej symulacji. Plansza ma postać dwuwymiarowiej płaszczyzny kartezjańskiej podzielonej na komórki (Cells). Na komórkach umieszczane są po kolei obiekty, tak, aby nie doszło do sytuacji, w której agent nie ma możliwości ruchu. Środowisko jest generowane dynamicznie w zależności od parametru **home-count**, który podajemy przy starcie skryptu. Dzięki temu możemy testować agenta w możliwie różnych sytuacjach. Opis elementów środowiska został przedstawiony przy użyciu paradygmatu obiektowego, dzięki temu kod jest czytelniejszy i wydzielony.
Na ten moment na planszy pojawiają się instancje klas:
*Grass* - klasa reprezentująca komórkę po której agent może swobodnie się poruszać, wypełnia większość mapy
*House* - klasa reprezentująca dom, agent wchodzi z nią w interakcję, zabiera wygenerowane śmieci
*Landfill* - klasa reprezentująca wysypisko, agent wchodzi z nią w interakcję, oddaje zebrane śmieci
*Garbage_collector* - klasa reprezentująca agenta - porusza się po mapie i wchodzi w interakcję z innymi obiektami
*HUD* - klasa reprezentująca HUD aplikacji (jeszcze niezaimplementowany)
**Struktura plików projektu**
--enums => Klasy dziedziczące po klasie *Enum*, ułatwiające parsowanie informacji
--fonts => Czcionki
--images => Obrazy i ikony używane w aplikacji
--raports => Raporty
--sprites => Klasy reprezentujące obiekty na mapie
.gitignore
config.py => Plik przechowujący funkcję zarządzające konfiguracją aplikacji
game.py => Plik rozruchowy programu
utils.py => Funkcje pomocnicze
README.md => Informacje o aplikacji
requirements.txt => Przechowuje informacje na temat używanych bibliotek
to_do.txt => Lista przyszłych zadań do zrobienia
### Reprezentacja Wiedzy
Przyjeliśmy na potrzeby projektu, że agent będzie wiedział co się dzieje na całym obszarze środowiska. W tym momencie wszystko co wie agent wyświetlane jest w oknie terminala, w czasie rzeczywistym, podczas trwania programu. Do informacji posiadanych przez agenta należą:
* Ile zebrano śmieci od startu programu
* Stopień zapełnienia śmieciarki
* Ile śmieci zostało na mapie
Podczas dalszego rozwoju powyższe informacje będą przedstawiane na ekranie aplikacji.
### Uruchamianie aplikacji
**Linux**
Uruchomienie standardowe (z 5 domami)
```bash
make init #stworzenie wirtualnego środowiska
make install #zainstalowanie zależności
make start #uruchomienie z domyślnym parametrem home-count=5
```
Uruchomienie niestandardowe
```bash
env/bin/python3 ./game.py home-count=amount #Liczba domów nie może być mniejsza niż 3
```
**Windows**
```powershell
py -m virtualenv env # Stworzenie wirtualnego środowiska
env\Scripts\pip.exe install -r requirements.txt
env\Scripts\python.exe ./game.py --home-count=amount
```

View File

@ -1,6 +1,6 @@
# Sztuczna inteligencja 2019 - Raport 2
**Czas trwania opisywanych prac:** 06.03.2018 - 26.03.2018
**Czas trwania opisywanych prac:** 06.03.2019 - 26.03.2019
**Członkowie zespołu:** Anna Nowak, Magdalena Wilczyńska, Konrad Pierzyński, Michał Starski
@ -13,29 +13,26 @@
#### Implementacja
Pierwszym podejściem naszej grupy do rozwiązania problemu była implementacja
algorytmu przeszukiwania drzewa w głąb - DFS (Wersja iteracyjna).
algorytmu przeszukiwania drzewa w głąb - DFS (Wersja rekurencyjna).
Aby zaimplementować ten algorytm, niezbędne było przygotowanie dla niego kilku
struktur pomocniczych dzięki którym będziemy mogli jasno zdefiniować warunki stopu i uzyskać satysfakcjonujące nas rozwiązanie.
Do użytych struktur należą:
- **Lista dwuwymiarowa przedstawiająca mapę w formie siatki po której można łatwo iterować** - Jeden stan takiej listy traktowaliśmy jako wierzchołek grafu
- **Stos wykonanych przez algorytm ruchów** - Używany do przechodzenia do kolejnych możliwych stanów jak i zapamiętania rozwiązania problemu.
- **Lista możliwych ruchów do wykonania przez agenta przy konkretnym stanie mapy**
- **Licznik głębokości na którą zszedł algorytm** - Zapobiega zajściu za głęboko w przypadku braku rozwiązania
**Przebieg algorytmu**:
- Dodaj do stosu pierwszy krok
- Dopóki stos nie jest pusty:
1. Weź stan mapy ze stosu (operacja stack.pop())
2. Sprawdź warunek końca (Czy problem został rozwiązany ?)
- Jeżeli tak, zakończ algorytm
1. Sprawdź czy w pobliżu śmieciarki znajduje się nieopróżnioony domek
2. Jeżeli możliwa jest jakaś akcja (zebranie/oddanie smieci) wykonaj ją
- Jeżeli akcja została wykonana, zakończ algorytm
- Jeżeli nie, sprawdź czy głębokość przekroczyła 30
1. Jeżeli tak, zakończ algorytm informacją o braku rozwiązania
2. Jeżeli nie, kontynuuj algorytm
3. Dla każdego możliwego kierunku wykonaj algorytm od punktu 1
Rozwiązanie następuje wtedy, gdy wszystkie śmieci zostaną zebrane przez agenta.
Rozwiązanie następuje wtedy, gdy domek zostaje opróżniony. Algorytm zostaje wywołany tyle samo razy, ile jest domków. Agent nie zna położenia domków na mapie podczas działania algorytmu.
#### Obserwacje

64
Raports/SI_Raport_3.md Normal file
View File

@ -0,0 +1,64 @@
# Sztuczna inteligencja 2019 - Raport 3
**Czas trwania opisywanych prac:** 03.04.2019 - 14.04.2019
**Członkowie zespołu:** Anna Nowak, Magdalena Wilczyńska, Konrad Pierzyński, Michał Starski
**Wybrany temat:** Inteligentna śmieciarka
**Link do repozytorium projektu:** https://git.wmi.amu.edu.pl/s440556/SZI2019SmieciarzWmi
## Planowanie ruchu - Algorytmy BFS i Best-first search
#### Implementacja
##### BFS (Iteracyjnie)
Algorytm przeszukiwania drzewa w głąb.
Algorytm jest niepoinformowany.
W przypadku BFS użyte struktury pozostają w gruncie rzeczy te same (z tym, że tym razem zamiast stosu do przechowywania stanu używamy kolejki), zmienia się tylko kolejność wykonywanych instrukcji:
**Przebieg algorytmu**:
- Dodaj do kolejki pierwszy krok
- Dopóki kolejka nie jest pusta:
1. Zdejmij z kolejki następny nieodwiedzony wierzchołek grafu
2. Jeżeli możliwa jest jakaś akcja (zebranie/oddanie smieci) wykonaj ją
3. Sprawdź wszystkie sąsiednie wierzchołki wybranego wierzchołka, które jeszcze nie zostały odwiedzone
##### Best-first search
Agent idzie w kierunku celu do którego jest najbliżej w linii prostej w danym momencie.
Algorytm jest poinformowany.
**Przebieg algorytmu**
1. Ustaw pozycję początkową
2. Znajdź obiekt w znajdujący się najbliżej w linii prostej
3. Jeżeli odległość od obiektu wynosi 1, wykonaj interakcję i usuń obiekt z listy celów, zwróć ścieżkę do obiektu
4. Jeżeli przekroczono limit rekursji lub nie można wykonać kroku zakończ
5. Na podstawie pozycji nowoobranego celu wybierz preferowane oraz niechciane kierunki poruszania się
6. Posortuj dozwolone ruchy zgodnie z preferencjami
7. Dla każdego kierunku na liście przeszukuj dostępne ścieżki dopóki jakakolwiek nie zostanie znaleziona
-----------
Ponadto, dołożyliśmy śmieciarzowi możliwość oddawania śmieci na wysypisko w ten sposób kompletując założenia planszy.
#### Obserwacje
W porównaniu do poprzednio zaimplementowanego DFS oba algorytmy sprawują się zdecydowanie szybciej. Przez to, że szukanie drogi nie odbywa się w głąb, agent nie traci czasu na przeszukiwanie wierzchołków z góry skazanych na porażkę. Poniżej przedstawiamy tabelę mierzącą liczbę kroków, która była potrzebna do wykonania przez agenta przy użyciu DFS, BFS i Best-first search na 5 przygotowanych do testów mapach:
| Algorytm / Kroki | Mapa 1 | Mapa 2 | Mapa 3 | Mapa 4 | Mapa 5 |
| --- | --- | --- | --- | --- | --- |
| DFS | 134 | 45 | 67 | 191 | 12 |
| BFS | 62 | 23 | 57 | 101 | 12 |
| Best-first | 55 | 20 | 58 | 99 | 12 |
Po wykonaniu testów możemy stwierdzić, że najlepszym z tych 3 algorytmów okazał się Best-first search. Warto jednak zauważyć, że różnica kroków jest mała.
Co widać bez jakichkolwiek wątpliwości DFS okazał się najgorszy (zgodnie z uzasadnieniem znajdującym się w raporcie nr. 2). Liczba kroków jest prawie dwukrotnie większa od tej w konkurujących algorytmach.
(Mapa 5 posiadała tylko jedną możliwe przejście posiadające 12 kroków ten wynik można pominąć.)

89
Raports/SVM.md Normal file
View File

@ -0,0 +1,89 @@
---
# SVM raport
##### Konrad Pierzyński
###### Śmieciarz
12.06.2019
---
**SVM** - **S**upport-**V**ector **M**achine - zestaw metod uczenia stosowanych głównie do klasyfikacji, której nauka ma na celu wyznaczenie płaszczyzn rozdzielających dane wejściowe na klasy.
![5d00b5469956838867](https://i.loli.net/2019/06/12/5d00b5469956838867.png)
---
### Przygotowanie danych
Dane uczące zostały wygenerowane w następujący sposób:
+ Program generuje losową mapę o określonych wymiarach
+ Uruchamiany jest jeden z algorytmów (*BestFirstSearch*), który generuje listę ruchów.
+ Do zestawu uczącego dopisywana jest para składająca się na ruch i otoczenie gracza.
- Ruch odpowiada kierunkom: góra, prawo, dół, lewo i akcji zebrania/oddania śmieci - odpowienio liczbowo 1, 2, 3, 4, 99
- Otocznie to tablica dwuwymiarowa 7x7, gdzie element środkowy to pozycja gracza. Tablica ta następnie spłaszczana jest do tablicy jednowymiarowej
- Każdy 'domek', na którym została wykonana już akcja zebrania i jest opróżniony, widoczny jest na mapie tak samo jak element otoczenia, z którym gracz nie może wejść w żadną interakcję (stanąć, zebrać)
- Jeśli siatka 7x7 wykracza swoim zakresem za mapę, siatka uzupełniana jest przez trawę, czyli obiekt, z którym gracz nie wchodzi w interakcję
+ Po przejściu całej mapy algorytmem i zebraniu danych proces jest powtarzany tak długo, by zgromadzić około tysiąc rozwiązanych map
Pojedynczy zestaw danych jest zapisywany jako json postaci:
```json
{
"maps": [
[Int, Int, ...],
[Int, Int, ...],
...
],
"moves":
[
Int, Int, ...
]
}
```
I dopisywany do głównej struktury:
```json
{
"moveset": [
Zestaw, Zestaw, ...
]
}
```
---
### Uczenie
Do przeprowadzenia procesu uczenia dane uczące zostały podzielone na dwie listy:
- Pierwsza lista X zawiera wszystkie mapy częściowe (otoczenia)
```X = [ [Int, Int, ...], [Int, Int, ...], ... ]```
- Druga lista y zawiera odpowiadające mapom ruchy (1,2,3,4,99), które wykonał algorytm (*BestFirstSearch*) na danych otoczeniach.
```y = [ Int, Int, ... ]```
Wyżej wymienione dwie listy zostały podane jako argument metodzie ```fit(X,y)```, która odpowiada za uczenie się SVM. Natomiast utworzenie samego obiektu polega na zaimportowaniu biblioteki *scikit-learn*:
```from sklearn import svm```
a następnie już same utworzenia obiektu svm:
```clf = svm.SVC(gamma='scale')```
Wyuczony obiekt jest zapisywany do pliku, dzięki modułowi ```pickle```, aby nie przeprowadzać procesu uczenia za każdym uruchomieniem programu.
---
### Wykonywanie ruchów
Do przewidywania ruchów wystarczy użyć metody ```predict([ [otoczenie] ])``` , które przyjmuje mapę częściową, a jej wynik jest akcją, którą powinien wykonać gracz. Wynik metody przekazywany jest graczowi, który wykonuje ruch.

92
Raports/TREE.md Normal file
View File

@ -0,0 +1,92 @@
# Sztuczna Inteligencja 2019 - Raport Indywidualny
**Czas trwania opisywanych prac:** 09.05.2019 - 11.06.2019
**Autor:** Michał Starski
**Wybrany temat:** Inteligentna śmieciarka
**Link do repozytorium projektu:** https://git.wmi.amu.edu.pl/s440556/SZI2019SmieciarzWmi
## Wybrany algorytm uczenia - drzewa decyzyjne
### Przygotowane dane
Aby zapewnić smieciarce jak najlepszy wynik, do przygotowania danych do uczenia wybrałem algorytm szukania najkrótszej ścieżki, który dawał najlepsze wyniki podczas projektu grupowego - **BestFS**.
Podczas każdego jednorazowego przebiegu algorytmu BestFS patrzyłem na to jaki krok śmieciarka wykonuje w danej sytuacji, a następnie dane kroki zapisywałem do pliku w formacie json, tworząc próbki do późniejszej nauki.
Przykładowa próbka w formacie json:
```json
{
"moveset": [
{
"maps": [[1, 1, 3, 4, 2, 2, 2, 2, 1], [2, 1, 1, 3, 1, 4, 1, 1, 1]],
"moves": [1, 2]
}
]
}
```
`moveset` to tablica wszystkich próbek wykorzystywanych do nauki.
Każdy element tablicy to obiekt posiadający dwa pola:
`maps` - otoczenie śmieciarki w danym kroku,
`moves` - ruch śmieciarki przy danym otoczeniu
W powyższym przykładzie dla czytelności, zostały przedstawione otoczenia 3x3 wokół śmieciarki. W implementacji obszar ten został powiększony do 7x7 w celu poprawienia dokładności algorytmu.
#### Maps
Spłaszczona tablicę dwuwymiarową przedstawiająca otoczenie śmieciarki w konkretnym momencie działania algorytmu. Każda z cyfr przedstawia inny obiekt na mapie:
- 1 - Trawa (Grass)
- 2 - Droga (Road)
- 3 - Wysypisko (Dump)
- 4 - Dom (House)
Dla powyższego przykładu pierwsza sytuacja (`moveset[0].maps[0]`) przedstawia następujące otoczenie na mapie
```
G G D
H R R
R R G
```
#### Moves
Tablica ruchów śmieciarki. i-ty ruch w tablicy odpowiada i-temu otoczeniu. Wyróżnimay 5 różnych ruchów agenta:
- 1 - Lewo
- 2 - Prawo
- 3 - Dół
- 4 - Góra
- 99 - Zbierz śmieci
Tak więc dla powyższego otoczenia `1` będzie oznaczać, że agent ruszył się w lewo.
---
### Implementacja
Do implementacji uczenia poprzez drzewo decyzyjny wykorzystałem bibliotekę [scikit learn](https://scikit-learn.org) do języka **python**. Podając odpowiednie dane, biblioteka przygotuje nam model zdolny do samodzielnego poruszania się na mapie.
```python
#Trenowanie modelu
from sklearn import tree
X = [Kolejne otoczenia 7x7 w danym kroku]
Y = [Kolejne kroki odpowiednie dla danego otoczenia]
clf = tree.DecisionTreeClassifier()
clf = clf.fit(X, Y)
#Samodzielny ruch wytrenowanego modelu
clf.predict([Otoczenie agenta])
```
`clf.predict` zwróci nam 1 z 5 ruchów, które ma wykonać agent.
---
### Obserwacje
W idealnym przypadku wytrenowany model powinien odzwierciedlać algorytm BestFS, jako iż to na podstawie jego był trenowany i to jego decyzje starał się naśladować. W rzeczywistości jednak po przygotowaniu ok. 1000 próbek agent radził sobie różnorako. Na jednych mapach poruszał się dość sprawnie, jednak na wielu nie wiedział co ma robić. Przyczyny mogą być różne, jednak w mojej opinii, przygotowanych danych było jednak trochę za mało i gdyby dać o wiele więcej danych do wytrenowania modelu, rezultat byłby o wiele lepszy.

68
Traversal/BestFS.py Normal file
View File

@ -0,0 +1,68 @@
from utilities import movement,check_moves
from DataModels.House import House
from DataModels.Container import Container
from config import GRID_WIDTH, GRID_HEIGHT
from math import sqrt
INF = float('Inf')
def CalculateDistance(gc, object_list):
min_distance_goal = ['-',INF]
for h in object_list:
distance = sqrt(pow(h[1][0]-gc[0],2)+pow(h[1][1]-gc[1],2))
if(min_distance_goal[1] > distance):
min_distance_goal = [h[1], distance]
return min_distance_goal
def BestFS(grid, available_movement, gc_moveset, object_list, depth = 0):
x, y = gc_moveset[-1][0], gc_moveset[-1][1]
#calculate distance to the nearest object
min_distance_goal = CalculateDistance([x,y], object_list)
if(min_distance_goal[1] == 1):
gc_moveset.append("pick_garbage")
cell = grid[min_distance_goal[0][0]][min_distance_goal[0][1]]
object_list.remove([cell,min_distance_goal[0]])
return([x, y], gc_moveset, object_list)
#if depth exceeded, return
if(depth > 15 or len(available_movement) == 0):
return
#set preffered directions based on the closest object
preffered_directions = []
discouraged_directions = []
if(min_distance_goal[0][0] > x):
preffered_directions.append("right")
if(min_distance_goal[0][0] < x):
preffered_directions.append("left")
if(min_distance_goal[0][1] > y):
preffered_directions.append("down")
if(min_distance_goal[0][1] < y):
preffered_directions.append("up")
if(len(preffered_directions) == 1):
discouraged_directions.append(movement(grid, x, y)[1][preffered_directions[0]])
#sort available moves according to preferences
sorted = [o for o in preffered_directions if o in available_movement]
for o in sorted:
available_movement.remove(o)
sorted.extend([o for o in available_movement if o not in discouraged_directions])
for o in sorted:
if(o in available_movement):
available_movement.remove(o)
sorted.extend(available_movement)
available_movement = sorted.copy()
for direction in available_movement:
x_next, y_next = movement(grid,x,y)[0][direction]
available_movement_next = check_moves(grid, x_next,y_next,direction)
gc_moveset_next = gc_moveset.copy()
gc_moveset_next.append([x_next,y_next])
result = BestFS(grid, available_movement_next, gc_moveset_next, object_list, depth + 1)
if result!= None:
return result

View File

@ -1,10 +1,17 @@
import sys, random
from MapGenerator import GenerateMap
CELL_SIZE = 64
FPS = 60
DELAY = 500
DELAY = 50
map = open( sys.argv[1], 'r' )
try:
MAP_NAME = sys.argv[1]
except:
MAP_NAME = GenerateMap()
map = open( MAP_NAME, 'r' )
GRID_WIDTH, GRID_HEIGHT = [int(x) for x in map.readline().split()]
GC_X, GC_Y = [int(x) for x in map.readline().split()]

12
main.py Executable file → Normal file
View File

@ -3,7 +3,7 @@
import pygame
import sys
from random import randint
from config import WINDOW_HEIGHT, WINDOW_WIDTH, GRID_HEIGHT, GRID_WIDTH, HOUSE_CAPACITY, FPS, GC_X, GC_Y
from config import WINDOW_HEIGHT, WINDOW_WIDTH, GRID_HEIGHT, GRID_WIDTH, HOUSE_CAPACITY, FPS, GC_X, GC_Y, MAP_NAME
from PIL import Image,ImageDraw
@ -21,7 +21,7 @@ dump_count=0
FPS_CLOCK = pygame.time.Clock()
GAME_WINDOW = pygame.display.set_mode((WINDOW_WIDTH, WINDOW_HEIGHT), 0, 32)
map = open(sys.argv[1], 'r')
map = open(MAP_NAME, 'r')
map.readline()
map.readline()
@ -100,6 +100,8 @@ while True:
gc.collect(map_objects)
elif event.key == pygame.K_0:
gc.find_houses(map_objects,house_count,dump_count, "DFS")
elif event.key == pygame.K_9:
gc.find_houses_BestFS(map_objects)
elif event.key == pygame.K_8:
gc.find_houses(map_objects,house_count,dump_count, "BFS")
@ -108,12 +110,12 @@ while True:
pygame_sprites.draw(GAME_WINDOW)
#draw GC moves
bg_rect = pygame.Surface((105,30), pygame.SRCALPHA) # per-pixel alpha
bg_rect.fill((0,0,0,160)) # notice the alpha value in the color
bg_rect = pygame.Surface((105,30), pygame.SRCALPHA)
bg_rect.fill((0,0,0,160))
GAME_WINDOW.blit(bg_rect, (0, WINDOW_HEIGHT-30))
font = pygame.font.SysFont("monospace", 15)
gc_moves = font.render("Moves: " + str(gc.get_moves_made()), 1, (255,255,255))
gc_moves = font.render("Moves: " + str(gc.get_moves_count()), 1, (255,255,255))
GAME_WINDOW.blit(gc_moves, (10, WINDOW_HEIGHT - 25))
pygame.display.flip()

View File

@ -1,5 +1,7 @@
from config import GRID_WIDTH, GRID_HEIGHT
from DataModels.Road import Road
import json, os, platform
def movement(environment, x ,y):
movement = {
"right": (x + 1, y) if x + 1 < GRID_WIDTH and type(environment[x + 1][y]) == Road else (x, y),
@ -21,3 +23,31 @@ def check_moves(environment, x,y,direction=None):
if direction == None:
return ([dir for dir in movement(environment, x, y)[0] if movement(environment, x,y)[0][dir] != (x,y)])
return ([dir for dir in movement(environment, x, y)[0] if movement(environment, x,y)[0][dir] != (x,y) and dir != movement(environment,x,y)[1][direction]])
def save_moveset(moveset):
if platform.system() == 'Windows':
path = '\moveset_data.json'
else:
path = '/moveset_data.json'
output_file = os.path.normpath(os.getcwd()) + path
results = {}
try:
f = open(output_file, 'r+')
except:
open(output_file, 'a').close()
finally:
f = open(output_file, 'r+')
try:
results = json.load(f)
except:
pass
finally:
if "moveset" not in results:
results = { "moveset": [] }
results["moveset"].append(moveset)
f.seek(0)
json.dump(results, f, indent=1)
f.close()