diff --git a/.idea/ProjektAI.iml b/.idea/ProjektAI.iml index d9e6024..d25211e 100644 --- a/.idea/ProjektAI.iml +++ b/.idea/ProjektAI.iml @@ -1,8 +1,10 @@ - - + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml index a5061af..035315d 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -3,5 +3,5 @@ - + \ No newline at end of file diff --git a/kelner/gui/GAdialog/GADefaults.py b/kelner/gui/GAdialog/GADefaults.py new file mode 100644 index 0000000..8686454 --- /dev/null +++ b/kelner/gui/GAdialog/GADefaults.py @@ -0,0 +1,104 @@ + +class CrossingOverMethod: + FixedQuadrant = 0 + SingleHorizontalDiv = 1 + SingleVerticalDiv = 2 + DoubleHorizontalDiv = 3 + DoubleVerticalDiv = 4 + RandomChoice = 5 + + +class SelectionMethod: + Roulette = 0 + Tournament = 1 + + +class MutationMethod: + Flip = 0 + Swap = 1 + + +class GADefaults: + + def __init__(self): + + self.defSelectionMethods = [("ruletka", SelectionMethod.Roulette), + ("turniej", SelectionMethod.Tournament)] + + self.defCrossingOverMethods = [("pojedynczy poziomy", CrossingOverMethod.SingleHorizontalDiv), + ("pojedynczy pionowy", CrossingOverMethod.SingleVerticalDiv), + ("podwójny poziomy", CrossingOverMethod.DoubleHorizontalDiv), + ("podwójny pionowy", CrossingOverMethod.DoubleVerticalDiv), + ("ćwiartki", CrossingOverMethod.FixedQuadrant), + ("losowe", CrossingOverMethod.RandomChoice)] + + self.defMutationMethods = [("inwersja", MutationMethod.Flip), + ("wymiana", MutationMethod.Swap)] + + self.windowName = "Algorytm genetyczny - parametry" + self.windowGeometry = "400x400" + + self.minTablesCount = 1 + self.maxTablesCount = 100 + self.defTablesCount = 20 + self.runTablesCount = self.defTablesCount + self.sliderNameTablesCount = "stoliki" + + self.minPopulationSize = 4 + self.maxPopulationSize = 30 + self.defPopulationSize = 5 + self.runPopulationSize = self.defPopulationSize + self.sliderNamePopulationSize = "populacja" + + self.minMutation = 0 + self.maxMutation = 10 + self.defMutation = 1 + self.runMutation = self.defMutation + self.sliderNameMutation = "mutacje" + + self.minGenerationsNumber = 1 + self.maxGenerationsNumber = 1000 + self.defGenerationsNumber = 20 + self.runGenerationsNumber = self.defGenerationsNumber + self.sliderNameGenerationsNumber = "pokolenia" + + self.defInfoFold = 5 + self.runInfoFold = self.defInfoFold + self.sliderInfoFold = "co ile" + + self.defSelectionMethod = SelectionMethod.Tournament + self.runSelectionMethod = self.defSelectionMethod + self.radioSelectionMethodName = "metoda selekcji" + + self.defCrossingOverMethod = CrossingOverMethod.SingleHorizontalDiv + self.runCrossingOverMethod = self.defCrossingOverMethod + self.radioCrossingOverMethodName = "metoda krzyżowania" + + self.defMutationMethod = MutationMethod.Swap + self.runMutationMethod = self.defMutationMethod + self.radioMutationMethodName = "metoda mutacji" + + self.minElitism = 0 + self.maxElitism = 50 + self.defElitism = 0 + self.runElitism = self.defElitism + self.sliderElitismName = "elitarność [%]" + + self.buttonStartName = "generuj" + self.buttonDefaultsName = "przywróć" + + self.__forbiddenPlaces = None + + self.waiterPosition = None + self.kitchenPosition = None + + def getForbiddenPlaces(self): + if self.__forbiddenPlaces is None: + self.__forbiddenPlaces = [self.waiterPosition, self.kitchenPosition] + return self.__forbiddenPlaces + + def getInfo(self): + return "populacja: " + str(self.runPopulationSize) \ + + ", pokolenia: " + str(self.runGenerationsNumber) \ + + ", stoliki: " + str(self.runTablesCount) \ + + ", mutacje: " + str(self.runMutation) diff --git a/kelner/gui/GAdialog/GADialog.py b/kelner/gui/GAdialog/GADialog.py new file mode 100644 index 0000000..fd4d6c2 --- /dev/null +++ b/kelner/gui/GAdialog/GADialog.py @@ -0,0 +1,82 @@ +import tkinter +from tkinter import * + +from kelner.gui.GAdialog.GADefaults import CrossingOverMethod + + +class GADialog: + + def __init__(self, defaults): + self.__defaults = defaults + self.window = tkinter.Tk() + self.window.attributes('-topmost', 'true') + self.window.title(defaults.windowName) + self.__sliderTablesCount = self.__getSlider(0, defaults.minTablesCount, defaults.maxTablesCount, defaults.runTablesCount, defaults.sliderNameTablesCount) + self.__sliderPopulationSize = self.__getSlider(1, defaults.minPopulationSize, defaults.maxPopulationSize, defaults.runPopulationSize, defaults.sliderNamePopulationSize) + self.__sliderMutation = self.__getSlider(2, defaults.minMutation, defaults.maxMutation, defaults.runMutation, defaults.sliderNameMutation) + self.__sliderGenerationsNumber = self.__getSlider(3, defaults.minGenerationsNumber, defaults.maxGenerationsNumber, defaults.runGenerationsNumber, defaults.sliderNameGenerationsNumber) + self.__sliderInfoFold = self.__getSlider(4, defaults.minGenerationsNumber, defaults.maxGenerationsNumber, defaults.runInfoFold, defaults.sliderInfoFold) + self.__sliderElitism = self.__getSlider(5, defaults.minElitism, defaults.maxElitism, defaults.runElitism, defaults.sliderElitismName) + + self.__radioSelectionMethodValue = IntVar() + self.__radioSelectionMethodValue.set(self.__defaults.runSelectionMethod) + self.__getRadioButton(6, defaults.defSelectionMethods, self.__radioSelectionMethodValue, defaults.radioSelectionMethodName) + + self.__radioCrossingOverMethodValue = IntVar() + self.__radioCrossingOverMethodValue.set(self.__defaults.runCrossingOverMethod) + self.__getRadioButton(8, defaults.defCrossingOverMethods, self.__radioCrossingOverMethodValue, defaults.radioCrossingOverMethodName) + + self.__radioMutationMethodValue = IntVar() + self.__radioMutationMethodValue.set(self.__defaults.runMutationMethod) + self.__getRadioButton(12, defaults.defMutationMethods, self.__radioMutationMethodValue, defaults.radioMutationMethodName) + + self.__buttonDefaults = self.__getButton(15, 0, W, defaults.buttonDefaultsName, self.__setDefaults) + self.__buttonStart = self.__getButton(15, 1, E, defaults.buttonStartName, self.__getAllValues) + self.window.mainloop() + + def __getSlider(self, rowNum, minVal, maxVal, runVal, labText): + label = Label(self.window, text = labText) + label.grid(row = rowNum, column = 0, sticky = S + W, padx = 5, pady = 5) + slider = Scale(self.window, variable = IntVar(), from_ = minVal, to = maxVal, orient=HORIZONTAL, length = 200) + slider.grid(row = rowNum, column = 1, sticky = E, padx = 5, pady = 3) + slider.set(runVal) + return slider + + def __getButton(self, rowNum, colNum, stickPos, btnText, action): + button = Button(self.window, text = btnText, command = action) + button.grid(row = rowNum, column = colNum, stick = stickPos, columnspan = 2, padx=60, pady=5) + return button + + def __getRadioButton(self, rowNum, methods, variable, labText): + label = LabelFrame(self.window, text = labText) + label.grid(row=rowNum, column=0, columnspan=2, padx=5, pady=5, sticky=W) + rowNum += 1 + iteration = 0 + for text, mode in methods: + radio = Radiobutton(label, text = text, variable = variable, value = mode) + radio.grid(row = rowNum, column = iteration % 2, sticky = W, padx = 5, pady = 3) + rowNum += iteration % 2 + iteration += 1 + + def __setDefaults(self): + self.__sliderTablesCount.set(self.__defaults.defTablesCount) + self.__sliderPopulationSize.set(self.__defaults.defPopulationSize) + self.__sliderMutation.set(self.__defaults.defMutation) + self.__sliderGenerationsNumber.set(self.__defaults.defGenerationsNumber) + self.__sliderInfoFold.set(self.__defaults.defInfoFold) + self.__sliderElitism.set(self.__defaults.defElitism) + self.__radioSelectionMethodValue.set(self.__defaults.defSelectionMethod) + self.__radioCrossingOverMethodValue.set(self.__defaults.defCrossingOverMethod) + self.__radioMutationMethodValue.set(self.__defaults.defMutationMethod) + + def __getAllValues(self): + self.__defaults.runTablesCount = self.__sliderTablesCount.get() + self.__defaults.runPopulationSize = self.__sliderPopulationSize.get() + self.__defaults.runMutation = self.__sliderMutation.get() + self.__defaults.runGenerationsNumber = self.__sliderGenerationsNumber.get() + self.__defaults.runInfoFold = self.__sliderInfoFold.get() + self.__defaults.runElitism = self.__sliderElitism.get() + self.__defaults.runSelectionMethod = self.__radioSelectionMethodValue.get() + self.__defaults.runCrossingOverMethod = self.__radioCrossingOverMethodValue.get() + self.__defaults.runMutationMethod = self.__radioMutationMethodValue.get() + self.window.destroy() diff --git a/kelner/gui/chart/Plots.py b/kelner/gui/chart/Plots.py new file mode 100644 index 0000000..4f4934c --- /dev/null +++ b/kelner/gui/chart/Plots.py @@ -0,0 +1,22 @@ +import matplotlib.pyplot as plt + + +class Plots: + + def __init__(self, generationNumber, bestFitnesses, bestTabless, worstFitnesses, worstTables, title): + self.__generationsNumber = generationNumber + self.__bestFitnesses = bestFitnesses + self.__bestTables = bestTabless + self.__worstTables = worstTables + self.__worstFitnesses = worstFitnesses + self.__title = title + + def draw(self): + generations = [i for i in range(self.__generationsNumber + 1)] + plt.figure(num = self.__title) + plt.plot(generations, self.__bestTables, label = "stoliki najlepszego") + plt.plot(generations, self.__bestFitnesses, label="fitness najlepszego") + plt.plot(generations, self.__worstTables, label = "stoliki najgorszego") + plt.plot(generations, self.__worstFitnesses, label = "fitness najgorszego") + plt.legend() + plt.show() diff --git a/kelner/images/kitchen.png b/kelner/images/kitchen.png index f26d55a..5c31f21 100644 Binary files a/kelner/images/kitchen.png and b/kelner/images/kitchen.png differ diff --git a/kelner/images/kurczak.png b/kelner/images/kurczak.png index 22af728..dedaf48 100644 Binary files a/kelner/images/kurczak.png and b/kelner/images/kurczak.png differ diff --git a/kelner/main.py b/kelner/main.py index 2d731eb..8e5d241 100644 --- a/kelner/main.py +++ b/kelner/main.py @@ -1,25 +1,24 @@ import pygame + from kelner.src.components.GridBoard import GridBoard from kelner.src.components.Waiter import Waiter -from kelner.src.components.Table import Table from kelner.src.components.Kitchen import Kitchen from kelner.src.managers.DrawableCollection import DrawableCollection + +# create screen consts from kelner.src.managers.MenuManager import MenuManager -from kelner.src.managers.TableManager import TableManager -from kelner.src.managers.WaiterManager import WaiterManager +from kelner.src.managers.TableGenerator import TableGenerator from kelner.src.algorithms.DecisionTree import Tree_Builder from kelner.src.managers.KitchenManager import KitchenManager from kelner.src.algorithms.CNN.PrepareData import LoadModelThread import kelner.src.settings as settings -import time -# create screen consts -Scale = 2 # scale for all images used within project -CellSize = round(50 * Scale) # pixel size of 1 square cell in the grid -PaintOffset = CellSize # pixel size of paint offset for all drawables -GridCountX = 15 # number of columns in grid -GridCountY = 9 # number of rows in grid -ScreenWidth = CellSize * GridCountX + 2 * PaintOffset # screen width in pixels +Scale = 1.5 # scale for all images used within project +CellSize = round(50 * Scale) # pixel size of 1 square cell in the grid +PaintOffset = CellSize # pixel size of paint offset for all drawables +GridCountX = 15 # number of columns in grid +GridCountY = 9 # number of rows in grid +ScreenWidth = CellSize * GridCountX + 2 * PaintOffset # screen width in pixels ScreenHeight = CellSize * GridCountY + 2 * PaintOffset # screen height in pixels running_tasks = {'table': [], 'waiter': []} @@ -34,8 +33,10 @@ load_model_thread.start() # joining this thread to main thread. Man thread will be started after this finish load_model_thread.join() +kitchenManager = KitchenManager(gridBoard) + # initialize drawable objects manager -drawableManager = DrawableCollection() +drawableManager = DrawableCollection(kitchenManager) # initialize menu manager menuManager = MenuManager() @@ -75,48 +76,16 @@ else: print("test4: fail") # initialize waiter component -waiter1 = Waiter(0, 0, 0, GridCountX - 1, 0, GridCountY - 1, CellSize, PaintOffset) -# waiter2 = Waiter(0, GridCountY - 1, 0, GridCountX - 1, 0, GridCountY - 1, CellSize, PaintOffset) -# waiter3 = Waiter(GridCountX - 1, 0, 0, GridCountX - 1, 0, GridCountY - 1, CellSize, PaintOffset) -# waiter4 = Waiter(GridCountX - 1, GridCountY - 1, 0, GridCountX - 1, 0, GridCountY - 1, CellSize, PaintOffset) - +waiter1 = Waiter(7, 4, 0, GridCountX - 1, 0, GridCountY - 1, CellSize, PaintOffset) # adds waiter to drawable collection drawableManager.add(waiter1) -# drawableManager.add(waiter2) -# drawableManager.add(waiter3) -# drawableManager.add(waiter4) - -kitchen = Kitchen(5, GridCountX - 5, 5, GridCountY - 5, CellSize, PaintOffset) +kitchen = Kitchen(14, 0, 5, GridCountX - 5, 5, GridCountY - 5, CellSize, PaintOffset) drawableManager.add(kitchen) -kitchenManager = KitchenManager(drawableManager, gridBoard) -# My comment -# initialize a number of tables given in range -for i in range(0, 40): - table = Table(1, GridCountX - 2, 1, GridCountY - 2, CellSize, PaintOffset) - if drawableManager.generatePosition(table): - drawableManager.add(table) - -# new thread controlling tables -tableTask = TableManager(drawableManager, menuManager) -tableTask.start() -running_tasks['table'].append(tableTask) - -# new thread controlling waiter -waiter1Task = WaiterManager(drawableManager, [waiter1], kitchenManager, menuManager, tableTask) -waiter1Task.start() -running_tasks['waiter'].append(tableTask) - -# waiter2Task = WaiterManager(drawableManager, [waiter2]) -# waiter2Task.start() -# -# waiter3Task = WaiterManager(drawableManager, [waiter3]) -# waiter3Task.start() -# -# waiter4Task = WaiterManager(drawableManager, [waiter4]) -# waiter4Task.start() +tableGenerator = TableGenerator(GridCountX, GridCountY, 1, GridCountX - 2, 1, GridCountY - 2, CellSize, PaintOffset, drawableManager) +tableGenerator.start() # main loop running = True @@ -124,56 +93,12 @@ while running: for event in pygame.event.get(): if event.type == pygame.QUIT: - tableTask.stop() - waiter1Task.stop() - # waiter2Task.stop() - # waiter3Task.stop() - # waiter4Task.stop() + tableGenerator.stop() + drawableManager.stop() running = False - # handles keyboard events - if event.type == pygame.KEYDOWN: - if event.key == pygame.K_LEFT: - # checks if new waiter's position to the left is not occupied by other object - if drawableManager.isPositionAvailable(waiter1.getX() - 1, waiter1.getY()): - waiter1.moveLeft() - if event.key == pygame.K_RIGHT: - # checks if new waiter's position to the right is not occupied by other object - if drawableManager.isPositionAvailable(waiter1.getX() + 1, waiter1.getY()): - waiter1.moveRight() - if event.key == pygame.K_UP: - # checks if new waiter's position up is not occupied by other object - if drawableManager.isPositionAvailable(waiter1.getX(), waiter1.getY() - 1): - waiter1.moveUp() - if event.key == pygame.K_DOWN: - # checks if new waiter's position down is not occupied by other object - if drawableManager.isPositionAvailable(waiter1.getX(), waiter1.getY() + 1): - waiter1.moveDown() - - if event.key == pygame.K_w: - # checks if new waiter's position to the left is not occupied by other object - if drawableManager.isPositionAvailable(waiter1.getX() - 1, waiter1.getY() - 1): - waiter1.moveLeft() - waiter1.moveUp() - if event.key == pygame.K_d: - # checks if new waiter's position to the right is not occupied by other object - if drawableManager.isPositionAvailable(waiter1.getX() + 1, waiter1.getY() - 1): - waiter1.moveRight() - waiter1.moveUp() - if event.key == pygame.K_s: - # checks if new waiter's position up is not occupied by other object - if drawableManager.isPositionAvailable(waiter1.getX() + 1, waiter1.getY() + 1): - waiter1.moveDown() - waiter1.moveRight() - if event.key == pygame.K_a: - # checks if new waiter's position down is not occupied by other object - if drawableManager.isPositionAvailable(waiter1.getX() - 1, waiter1.getY() + 1): - waiter1.moveDown() - waiter1.moveLeft() - drawableManager.forceRepaint() - # repaints all objects to the screen - # is set only on initial paint or after keyboard event or call to forceRepaint() + # is set only on initial paint or call to forceRepaint() if drawableManager.mustRepaint(): if not kitchenManager.is_running(): gridBoard.reinitialize() diff --git a/kelner/src/algorithms/CNN/PrepareData.py b/kelner/src/algorithms/CNN/PrepareData.py index 763ed2b..e4b830b 100644 --- a/kelner/src/algorithms/CNN/PrepareData.py +++ b/kelner/src/algorithms/CNN/PrepareData.py @@ -8,9 +8,9 @@ from tensorflow.keras.models import load_model import kelner.src.settings as settings # currently all images are not stored in repo because of big weight (5 GB) -data_dir = 'D:\\Nauka\\Studia\\4_sem\\SztucznaInteligencja\\A_star\\CNN\\foodRecognitionCNN\\food-101\\images' -folder_dir = 'D:\\Nauka\\Studia\\4_sem\\SztucznaInteligencja\\A_star\\CNN\\foodRecognitionCNN\\food-101' -foods_sorted = sorted(os.listdir(data_dir)) +#data_dir = 'D:\\Nauka\\Studia\\4_sem\\SztucznaInteligencja\\A_star\\CNN\\foodRecognitionCNN\\food-101\\images' +#folder_dir = 'D:\\Nauka\\Studia\\4_sem\\SztucznaInteligencja\\A_star\\CNN\\foodRecognitionCNN\\food-101' +#foods_sorted = sorted(os.listdir(data_dir)) food_id = 0 diff --git a/kelner/src/algorithms/GATableGenerator/GATableGenerator.py b/kelner/src/algorithms/GATableGenerator/GATableGenerator.py new file mode 100644 index 0000000..0694bbf --- /dev/null +++ b/kelner/src/algorithms/GATableGenerator/GATableGenerator.py @@ -0,0 +1,304 @@ +import random +import math +from queue import PriorityQueue +from kelner.src.algorithms.GATableGenerator.Individual import Individual +from kelner.src.algorithms.AStar.Finder import Finder +from kelner.gui.GAdialog.GADefaults import GADefaults, CrossingOverMethod, MutationMethod, SelectionMethod + + +class GATableGenerator: + + def __init__(self, gridCols, gridRows, params): + self.gridCols = gridCols + self.gridRows = gridRows + self.params = params + self.currentGenerationNumber = 0 + self.population = None + self.bestFitnessScores = [] + self.worstFitnessScores = [] + self.bestTables = [] + self.worstTables = [] + + def generateTables(self): + table = [[0] * self.gridCols for i in range(self.gridRows)] + for _ in range(self.params.runTablesCount): + isPositionUnique = False + while not isPositionUnique: + x = random.randint(0, self.gridCols - 1) + y = random.randint(0, self.gridRows - 1) + if (x, y) not in self.params.getForbiddenPlaces() and table[y][x] == 0: + isPositionUnique = True + table[y][x] = 1 + table[self.params.kitchenPosition[1]][self.params.kitchenPosition[0]] = 1 + return table + + def getIndividual(self, table): + origin = self.params.waiterPosition + finder = Finder(table) + fitnessScore = 0 + tablesCount = 0 + for y in range(self.gridRows): + for x in range(self.gridCols): + if table[y][x] == 1: + tablesCount += 1 + targets = finder.getNeighbours((x, y), False) + for target in targets: + if origin == target: + fitnessScore += 1 + break + else: + path = finder.getPath(origin, target, True) + if path: + fitnessScore += 1 + break + individual = Individual(table, fitnessScore - (tablesCount - fitnessScore), tablesCount) + return individual + + def updateStats(self): + bestIndividual = self.getBestIndividual() + worstIndividual = self.getWorstIndividual() + self.bestFitnessScores.append(bestIndividual.getFitness()) + self.bestTables.append(bestIndividual.getTablesCount()) + self.worstFitnessScores.append(worstIndividual.getFitness()) + self.worstTables.append(worstIndividual.getTablesCount()) + + def firstPopulation(self): + self.population = PriorityQueue() + for _ in range(self.params.runPopulationSize): + self.population.put(self.getIndividual(self.generateTables())) + self.currentGenerationNumber = 0 + self.bestFitnessScores = [] + self.worstFitnessScores = [] + self.bestTables = [] + self.worstTables = [] + self.updateStats() + + def getPopulationQueue(self): + queue = PriorityQueue() + for item in self.population.queue: + queue.put(item) + return queue + + def getPopulationSortedArray(self): + queue = self.getPopulationQueue() + array = [] + while not queue.empty(): + array.append(queue.get()) + return array + + def arrayToQueue(self, array): + queue = PriorityQueue() + for item in array: + queue.put(item) + return queue + + def selectParentsByRoulette(self): + populationArray = self.getPopulationSortedArray() + parents = [] + delta = populationArray[len(populationArray) - 1].getFitness() + if delta > 0: + delta = 0 + else: + delta = abs(delta) + 1 + fitnessSum = 0 + for individual in populationArray: + fitnessSum += individual.getFitness() + delta + for _ in range(2): + draw = random.uniform(0, 1) + accumulated = 0 + for individual in populationArray: + probability = float(individual.getFitness() + delta) / fitnessSum + accumulated += probability + if draw <= accumulated: + parents.append(individual) + fitnessSum -= (individual.getFitness() + delta) + populationArray.remove(individual) + break + return parents + + def selectParentsByTournament(self): + parents = [] + competitorsNum = math.ceil(0.25 * self.params.runPopulationSize) + if competitorsNum < self.params.minPopulationSize: + competitorsNum = self.params.minPopulationSize - 1 + population = self.getPopulationSortedArray() + parent = self.arrayToQueue(random.sample(population, competitorsNum)).get() + population.remove(parent) + parents.append(parent) + parents.append(self.arrayToQueue(random.sample(population, competitorsNum)).get()) + return parents + + def quadrant(self, parentL, parentR): + childTables = [[0] * self.gridCols for i in range(self.gridRows)] + for row in range(self.gridRows): + for col in range(self.gridCols): + if (row <= math.floor((self.gridRows - 1) / 2.0)) and (col <= math.ceil((self.gridCols - 1) / 2.0)) or \ + (row > math.floor((self.gridRows - 1) / 2.0)) and (col > math.ceil((self.gridCols - 1) / 2.0)): + childTables[row][col] = parentL.getTables()[row][col] + else: + childTables[row][col] = parentR.getTables()[row][col] + return childTables + + def singleVerticalDiv(self, parentL, parentR): + childTables = [[0] * self.gridCols for i in range(self.gridRows)] + divX = random.randint(1, self.gridCols - 2) + for row in range(self.gridRows): + for col in range(self.gridCols): + if col <= divX: + childTables[row][col] = parentL.getTables()[row][col] + else: + childTables[row][col] = parentR.getTables()[row][col] + return childTables + + def singleHorizontalDiv(self, parentL, parentR): + childTables = [[0] * self.gridCols for i in range(self.gridRows)] + divY = random.randint(1, self.gridRows - 2) + for row in range(self.gridRows): + for col in range(self.gridCols): + if row <= divY: + childTables[row][col] = parentL.getTables()[row][col] + else: + childTables[row][col] = parentR.getTables()[row][col] + return childTables + + def doubleVerticalDiv(self, parentL, parentR): + childTables = [[0] * self.gridCols for i in range(self.gridRows)] + divC = math.ceil((self.gridCols - 1) / 2) + divX1 = random.randint(1, divC - 1) + divX2 = random.randint(divC + 1, self.gridCols - 2) + for row in range(self.gridRows): + for col in range(self.gridCols): + if divX1 <= col < divX2: + childTables[row][col] = parentL.getTables()[row][col] + else: + childTables[row][col] = parentR.getTables()[row][col] + return childTables + + def doubleHorizontalDiv(self, parentL, parentR): + childTables = [[0] * self.gridCols for i in range(self.gridRows)] + divC = math.ceil((self.gridRows - 1) / 2) + divY1 = random.randint(1, divC - 1) + divY2 = random.randint(divC + 1, self.gridRows - 2) + for row in range(self.gridRows): + for col in range(self.gridCols): + if divY1 <= row < divY2: + childTables[row][col] = parentL.getTables()[row][col] + else: + childTables[row][col] = parentR.getTables()[row][col] + return childTables + + def randomChoice(self, parentL, parentR): + childTables = [[0] * self.gridCols for i in range(self.gridRows)] + parents = [parentL.getTables(), parentR.getTables()] + for row in range(self.gridRows): + for col in range(self.gridCols): + childTables[row][col] = random.choice(parents)[row][col] + return childTables + + def procreate(self): + if self.params.runSelectionMethod == SelectionMethod.Roulette: + parentLeft, parentRight = self.selectParentsByRoulette() + elif self.params.runSelectionMethod == SelectionMethod.Tournament: + parentLeft, parentRight = self.selectParentsByTournament() + child = None + if self.params.runCrossingOverMethod == CrossingOverMethod.FixedQuadrant: + child = self.quadrant(parentLeft, parentRight) + elif self.params.runCrossingOverMethod == CrossingOverMethod.SingleVerticalDiv: + child = self.singleVerticalDiv(parentLeft, parentRight) + elif self.params.runCrossingOverMethod == CrossingOverMethod.SingleHorizontalDiv: + child = self.singleHorizontalDiv(parentLeft, parentRight) + elif self.params.runCrossingOverMethod == CrossingOverMethod.DoubleVerticalDiv: + child = self.doubleVerticalDiv(parentLeft, parentRight) + elif self.params.runCrossingOverMethod == CrossingOverMethod.DoubleHorizontalDiv: + child = self.doubleHorizontalDiv(parentLeft, parentRight) + elif self.params.runCrossingOverMethod == CrossingOverMethod.RandomChoice: + child = self.randomChoice(parentLeft, parentRight) + return child + + def getRandTuple(self): + isPlaceAvailable = False + while not isPlaceAvailable: + randX = random.randint(0, self.gridCols - 1) + randY = random.randint(0, self.gridRows - 1) + isPlaceAvailable = (randX, randY) not in self.params.getForbiddenPlaces() + return randX, randY + + def mutateFlip(self, tables): + mutatedTables = tables + numberOfMutation = random.randint(0, self.params.runMutation) + for _ in range(numberOfMutation): + randX, randY = self.getRandTuple() + if mutatedTables[randY][randX] == 1: + mutatedTables[randY][randX] = 0 + else: + mutatedTables[randY][randX] = 1 + return mutatedTables + + def mutateSwap(self, tables): + mutatedTables = tables + numberOfMutation = random.randint(0, self.params.runMutation) + for _ in range(numberOfMutation): + randX1, randY1 = self.getRandTuple() + randX2, randY2 = self.getRandTuple() + value = mutatedTables[randY1][randX1] + mutatedTables[randY1][randX1] = mutatedTables[randY2][randX2] + mutatedTables[randY2][randX2] = value + return mutatedTables + + def makeNextGeneration(self): + tablesArray = [] + eliteSize = math.ceil((self.params.runPopulationSize * self.params.runElitism) / 100.0) + for _ in range(self.params.runPopulationSize - eliteSize): + if self.params.runMutationMethod == MutationMethod.Flip: + mutated = self.mutateFlip(self.procreate()) + elif self.params.runMutationMethod == MutationMethod.Swap: + mutated = self.mutateSwap(self.procreate()) + tablesArray.append(mutated) + eliteArray = [] + for _ in range(eliteSize): + eliteArray.append(self.population.get()) + self.population = PriorityQueue() + for individual in eliteArray: + self.population.put(individual) + for table in tablesArray: + self.population.put(self.getIndividual(table)) + self.currentGenerationNumber += 1 + self.updateStats() + + def canGenerate(self): + return self.currentGenerationNumber < self.params.runGenerationsNumber + + def getBestIndividual(self): + population = self.getPopulationQueue() + return population.get() + + def getWorstIndividual(self): + population = self.getPopulationSortedArray() + return population[len(population) - 1] + + def printBest(self): + populationBest = self.getBestIndividual() + populationBest.print(self.currentGenerationNumber) + + def printPopulation(self): + queue = self.getPopulationQueue() + while not queue.empty(): + individual = queue.get() + individual.print(self.currentGenerationNumber) + print('---------------------------------------') + + def makeAllGenerations(self): + self.printBest() + while self.canGenerate(): + self.makeNextGeneration() + self.printBest() + +""" +params = GADefaults() +generator = GATableGenerator(15, 9, params) +generator.firstPopulation() +generator.printPopulation() +while generator.canGenerate(): + generator.makeNextGeneration() + generator.printPopulation() +""" \ No newline at end of file diff --git a/kelner/src/algorithms/GATableGenerator/Individual.py b/kelner/src/algorithms/GATableGenerator/Individual.py new file mode 100644 index 0000000..76cad6f --- /dev/null +++ b/kelner/src/algorithms/GATableGenerator/Individual.py @@ -0,0 +1,33 @@ +class Individual: + + def __init__(self, table, fitnessScore, tablesCount): + self.__table = table + self.__fitnessScore = fitnessScore + self.__tablesCount = tablesCount + + # operator (>) for PriorityQueue comparison (determines the objects order) + def __gt__(self, other): + return self.__fitnessScore < other.__fitnessScore + + def getTables(self): + return self.__table + + def getTablesCount(self): + return self.__tablesCount + + def getFitness(self): + return self.__fitnessScore + + def getInfo(self, generation): + return f"GENERATION: {generation}, FITNESS: {self.__fitnessScore}, TABLES: {self.__tablesCount}" + + def print(self, generation): + print(self.getInfo(generation)) + cols = len(self.__table[0]) + rows = len(self.__table) + for row in range(rows): + for col in range(cols): + v = self.__table[row][col] + v = ' ' if v == 0 else '#' if v == 1 else 'O' + print('|', v, sep='', end='') + print('|') \ No newline at end of file diff --git a/kelner/src/components/Drawable.py b/kelner/src/components/Drawable.py index dd8fa7e..873d88e 100644 --- a/kelner/src/components/Drawable.py +++ b/kelner/src/components/Drawable.py @@ -1,5 +1,3 @@ -from kelner.src.managers.ImageCache import ImageCache, Images - class Drawable: @@ -62,15 +60,3 @@ class Drawable: def drawAux(self, screen): pass - - def getImage(self, imageKind, img_path=None): - if imageKind in [Images.Guest1, Images.Guest2, Images.Guest3, Images.Plate]: - size = int(self.getCellSize() / 3) - elif imageKind in [Images.Kitchen, Images.Dishes]: - size = int(self.getCellSize()) - else: - size = int(1.4 * self.getCellSize()) - if img_path: - return ImageCache.getInstance().getImage(imageKind, size, size, img_path) - else: - return ImageCache.getInstance().getImage(imageKind, size, size) diff --git a/kelner/src/components/Kitchen.py b/kelner/src/components/Kitchen.py index 75e9060..01244ce 100644 --- a/kelner/src/components/Kitchen.py +++ b/kelner/src/components/Kitchen.py @@ -1,18 +1,15 @@ import random -from enum import Enum -from threading import Lock from kelner.src.components.Drawable import Drawable from kelner.src.managers.ImageCache import ImageCache, Images from kelner.src.algorithms.DecisionTree import Tree_Builder import os -import copy -import time import pygame + class Kitchen(Drawable): - def __init__(self, minX, maxX, minY, maxY, cellSize, offset): + def __init__(self, x, y, minX, maxX, minY, maxY, cellSize, offset): # call base class constructor - super().__init__(14, 0, minX, maxX, minY, maxY, cellSize, offset) + super().__init__(x, y, minX, maxX, minY, maxY, cellSize, offset) self._preparing_orders = None self._ready_orders = None self._photos_path = os.path.join(os.getcwd(), 'foodImages') @@ -92,7 +89,17 @@ class Kitchen(Drawable): # print("Image drawing: {}".format(img_path)) return img_paths - + def getImage(self, imageKind, img_path=None): + if imageKind in [Images.Guest1, Images.Guest2, Images.Guest3, Images.Plate]: + size = int(self.getCellSize() / 3) + elif imageKind in [Images.Kitchen, Images.Dishes]: + size = int(self.getCellSize()) + else: + size = int(1.4 * self.getCellSize()) + if img_path: + return ImageCache.getInstance().getImage(imageKind, size, size, img_path) + else: + return ImageCache.getInstance().getImage(imageKind, size, size) def draw(self, screen): xBase = self.getX() * self.getCellSize() + self.getOffset() diff --git a/kelner/src/components/Table.py b/kelner/src/components/Table.py index 9e12681..2b6ea31 100644 --- a/kelner/src/components/Table.py +++ b/kelner/src/components/Table.py @@ -111,7 +111,7 @@ class Table(Drawable): self.__status = status def __getImage(self, imageKind): - if imageKind in [Images.Guest1, Images.Guest2, Images.Guest3, Images.Plate]: + if imageKind in [Images.Guest1, Images.Guest2, Images.Guest3, Images.Plate, Images.Chicken]: size = int(self.getCellSize() / 3) else: size = int(1.4 * self.getCellSize()) @@ -164,3 +164,15 @@ class Table(Drawable): screen.blit(imagePlate, (xBase + guest1XOffset, yBase + platesYOffset)) screen.blit(imagePlate, (xBase + guest2XOffset, yBase + platesYOffset)) screen.blit(imagePlate, (xBase + guest3XOffset, yBase + platesYOffset)) + elif self.isStatus(Status.Served): + platesYOffset = int(0.3 * self.getCellSize()) + imageChicken = self.__getImage(Images.Chicken) + if len(self.__guests) == 1: + screen.blit(imageChicken, (xBase + guest2XOffset, yBase + platesYOffset)) + elif len(self.__guests) == 2: + screen.blit(imageChicken, (xBase + guest4XOffset, yBase + platesYOffset)) + screen.blit(imageChicken, (xBase + guest5XOffset, yBase + platesYOffset)) + elif len(self.__guests) == 3: + screen.blit(imageChicken, (xBase + guest1XOffset, yBase + platesYOffset)) + screen.blit(imageChicken, (xBase + guest2XOffset, yBase + platesYOffset)) + screen.blit(imageChicken, (xBase + guest3XOffset, yBase + platesYOffset)) diff --git a/kelner/src/managers/DrawableCollection.py b/kelner/src/managers/DrawableCollection.py index 7339780..a8f8f06 100644 --- a/kelner/src/managers/DrawableCollection.py +++ b/kelner/src/managers/DrawableCollection.py @@ -7,16 +7,43 @@ from kelner.src.components.Kitchen import Kitchen # drawable objects manager +from kelner.src.managers.MenuManager import MenuManager +from kelner.src.managers.TableManager import TableManager +from kelner.src.managers.WaiterManager import WaiterManager + + class DrawableCollection: # const, minimal distance between objects __MinDistanceX = 1 __MinDistanceY = 0 - def __init__(self): + def __init__(self, kitchenManager): # collection that holds all drawable objects self.__mustRepaint = True self.__drawables = [] self.__waiterLock = Lock() + self.__tasks = [] + self.__kitchenManager = kitchenManager + + def start(self): + # initialize menu manager + menuManager = MenuManager() + # new thread controlling tables + tableTask = TableManager(self, menuManager) + self.__tasks.append(tableTask) + + waiters = self.getWaiters() + for waiter in waiters: + # new thread controlling waiter + waiterTask = WaiterManager(self, [waiter], self.__kitchenManager, menuManager, tableTask) + self.__tasks.append(waiterTask) + + for task in self.__tasks: + task.start() + + def stop(self): + for task in self.__tasks: + task.stop() # adds drawable objects to the collection def add(self, drawable): @@ -52,6 +79,10 @@ class DrawableCollection: break return isPositionAvailable + # deletes all tables + def delTables(self): + self.__drawables = [drawable for drawable in self.__drawables if not isinstance(drawable, Table)] + # gets all tables by status from collection def getTables(self, status): result = [] @@ -61,11 +92,10 @@ class DrawableCollection: return result def get_kitchen(self): - kitchen = None for item in self.__drawables: if isinstance(item, Kitchen): - kitchen = item - return kitchen + return item + return None def lock(self): self.__waiterLock.acquire() @@ -101,8 +131,8 @@ class DrawableCollection: nearestTables = [] tables = self.getTables(tableStatus) for table in tables: - if (table.getX() == waiter.getX() and abs(table.getY() - waiter.getY()) == 1) or \ - (table.getY() == waiter.getY() and abs(table.getX() - waiter.getX()) == 1): + if (table.getX() == waiter.getX() and abs(table.getY() - waiter.getY()) == 1) or\ + (table.getY() == waiter.getY() and abs(table.getX() - waiter.getX()) == 1): nearestTables.append(table) return nearestTables diff --git a/kelner/src/managers/ImageCache.py b/kelner/src/managers/ImageCache.py index e807e72..5a90c2c 100644 --- a/kelner/src/managers/ImageCache.py +++ b/kelner/src/managers/ImageCache.py @@ -23,6 +23,7 @@ class Images(Enum): ToolTip = 16 Kitchen = 17 Dishes = 18 + Chicken = 19 class ImageCache: @@ -60,7 +61,8 @@ class ImageCache: Images.Guest3: './images/wiking_rudy2.png', Images.ToolTip: './images/tooltip.png', Images.Kitchen: './images/kitchen.png', - Images.Dishes: './images/testDishes/' + Images.Dishes: './images/testDishes/', + Images.Chicken: './images/kurczak.png' } def __getFont(self): diff --git a/kelner/src/managers/KitchenManager.py b/kelner/src/managers/KitchenManager.py index 5ad48be..158e2aa 100644 --- a/kelner/src/managers/KitchenManager.py +++ b/kelner/src/managers/KitchenManager.py @@ -6,9 +6,8 @@ import sys # creates new thread class KitchenManager(threading.Thread): - def __init__(self, drawable_manager, gridboard): + def __init__(self, gridboard): super().__init__() - self._drawable_manager = drawable_manager self._gridboard = gridboard self.__runThread = False diff --git a/kelner/src/managers/TableGenerator.py b/kelner/src/managers/TableGenerator.py new file mode 100644 index 0000000..038c398 --- /dev/null +++ b/kelner/src/managers/TableGenerator.py @@ -0,0 +1,120 @@ +import threading +from tkinter import messagebox, Tk +from kelner.src.components.Table import Table +from kelner.src.algorithms.GATableGenerator.GATableGenerator import GATableGenerator +from kelner.gui.GAdialog.GADialog import GADialog +from kelner.gui.GAdialog.GADefaults import GADefaults +from kelner.gui.chart.Plots import Plots + + +# creates new thread +class TableGenerator (threading.Thread): + + def __init__(self, gridCols, gridRows, minX, maxX, minY, maxY, cellSize, paintOffset, drawableManager): + super().__init__() + self.__minX = minX + self.__maxX = maxX + self.__minY = minY + self.__maxY = maxY + self.__cellSize = cellSize + self.__paintOffset = paintOffset + self.__drawableManager = drawableManager + self.__runThread = True + self.__defaults = GADefaults() + self.__defaults.waiterPosition = self.__getDefaultWaiterPosition() + self.__defaults.kitchenPosition = self.__getDefaultKitchenPosition() + self.__gaTableGenerator = GATableGenerator(gridCols, gridRows, self.__defaults) + + def __getDefaultWaiterPosition(self): + waiters = self.__drawableManager.getWaiters() + if waiters: + return waiters[0].getX(), waiters[0].getY() + return None + + def __getDefaultKitchenPosition(self): + kitchen = self.__drawableManager.get_kitchen() + if kitchen is not None: + return kitchen.getX(), kitchen.getY() + return None + + def __randomTables(self): + # initialize a number of tables given in range + for i in range(0, 40): + table = Table(self.__minX, self.__maxX, self.__minY, self.__maxY, self.__cellSize, self.__paintOffset) + if self.__drawableManager.generatePosition(table): + self.__drawableManager.add(table) + + def __geneticTables(self): + bestIndividual = self.__gaTableGenerator.getBestIndividual() + for row in range(self.__gaTableGenerator.gridRows): + for col in range(self.__gaTableGenerator.gridCols): + if bestIndividual.getTables()[row][col] == 1 and \ + (row != self.__defaults.kitchenPosition[1] or col != self.__defaults.kitchenPosition[0]): + table = Table(0, self.__gaTableGenerator.gridCols - 1, 0, self.__gaTableGenerator.gridRows - 1, self.__cellSize, self.__paintOffset) + table.setX(col) + table.setY(row) + self.__drawableManager.add(table) + + def __askIfGA(self): + window = Tk() + window.attributes('-topmost', 'true') + window.wm_withdraw() + result = messagebox.askquestion("Rodzaj algorytmu", "Czy uruchomić algorytm genetyczny?", parent=window) + window.destroy() + return result == 'yes' + + def __ask(self, info): + window = Tk() + window.attributes('-topmost', 'true') + window.wm_withdraw() + result = messagebox.askquestion(info + '\n', "Czy nowa generacja?", parent=window) + window.destroy() + return result == 'yes' + + def __askIfAgain(self): + window = Tk() + window.attributes('-topmost', 'true') + window.wm_withdraw() + result = messagebox.askquestion("Potwierdź", "Czy powtórzyć proces?", parent=window) + window.destroy() + return result == 'yes' + + def __plot(self): + plot = Plots(self.__gaTableGenerator.currentGenerationNumber, + self.__gaTableGenerator.bestFitnessScores, self.__gaTableGenerator.bestTables, + self.__gaTableGenerator.worstFitnessScores, self.__gaTableGenerator.worstTables, + self.__defaults.getInfo()) + plot.draw() + + def run(self): + if self.__askIfGA(): + while self.__runThread: + again = True + GADialog(self.__defaults) + self.__gaTableGenerator.firstPopulation() + while again: + self.__drawableManager.delTables() + self.__geneticTables() + self.__drawableManager.forceRepaint() + if self.__gaTableGenerator.canGenerate(): + if self.__gaTableGenerator.currentGenerationNumber % self.__defaults.runInfoFold != 0 or\ + self.__ask(self.__gaTableGenerator.getBestIndividual().getInfo(self.__gaTableGenerator.currentGenerationNumber)): + self.__gaTableGenerator.makeNextGeneration() + else: + self.__drawableManager.start() + self.stop() + again = False + self.__plot() + else: + self.__plot() + if not self.__askIfAgain(): + self.__drawableManager.start() + self.stop() + again = False + else: + self.__randomTables() + self.__drawableManager.forceRepaint() + self.__drawableManager.start() + + def stop(self): + self.__runThread = False diff --git a/kelner/src/managers/WaiterManager.py b/kelner/src/managers/WaiterManager.py index 9c80ac6..dd6a65e 100644 --- a/kelner/src/managers/WaiterManager.py +++ b/kelner/src/managers/WaiterManager.py @@ -1,6 +1,7 @@ import threading import time import sys +from math import sqrt from kelner.src.components.Table import Status from kelner.src.algorithms.AStar.Finder import Finder from kelner.src.algorithms.BFS.BFS import BFS @@ -20,43 +21,65 @@ class WaiterManager(threading.Thread): self._menu_manager = menu_manager self._table_manager = table_manager - def __getNearestTargetPath(self, waiter, target): - distance = sys.maxsize - nearestTargetPath = None - reservedPlaces = self.__drawableManager.getReservedPlaces(waiter) + def __getDistance(self, waiter, tupleXY): + dx = waiter.getX() - tupleXY[0] + dy = waiter.getY() - tupleXY[1] + return sqrt(dx * dx + dy * dy) + + def __sortTargets(self, waiter, targets): + return sorted(targets, key=lambda target: self.__getDistance(waiter, (target[0], target[1]))) + + def __getTargets(self, waiter, finder): + found = [] tables = self.__drawableManager.getTables(Status.Ready) - finder = Finder(reservedPlaces) - origin = (waiter.getX(), waiter.getY()) - if target == 'kitchen': - path = finder.getPath(origin, (14, 1), True) - path2 = finder.getPath(origin, (13, 0), True) - path = path2 if len(path) > len(path2) else path - return path if tables: + origin = (waiter.getX(), waiter.getY()) for table in tables: if table.hasOrder(): targets = finder.getNeighbours((table.getX(), table.getY()), False) for target in targets: - if target is not None: - path = finder.getPath(origin, target, True) - if path: - result = len(path) - if result < distance: - distance = result - nearestTargetPath = path + if target == origin: + return [] + else: + found.append(target) + return self.__sortTargets(waiter, found) + + def __getNearestTargetPath(self, waiter, targets=None): + distance = sys.maxsize + nearestTargetPath = None + reservedPlaces = self.__drawableManager.getReservedPlaces(waiter) + finder = Finder(reservedPlaces) + origin = (waiter.getX(), waiter.getY()) + if targets is None: + targets = self.__getTargets(waiter, finder) + for target in targets: + if distance > self.__getDistance(waiter, target): + path = finder.getPath(origin, target, True) + if path: + result = len(path) + if result < distance: + distance = result + nearestTargetPath = path return nearestTargetPath def get_specified_path(self, waiter, table): + distance = sys.maxsize + nearestTargetPath = None reserved_places = self.__drawableManager.getReservedPlaces(waiter) finder = Finder(reserved_places) origin = (waiter.getX(), waiter.getY()) targets = finder.getNeighbours((table[0], table[1]), False) for target in targets: - if target is not None: + if distance > self.__getDistance(waiter, target): path = finder.getPath(origin, target, True) if path: - return path + result = len(path) + if result < distance: + distance = result + nearestTargetPath = path + + return nearestTargetPath def __changeWaiterDirection(self, waiter, x, y): targetDirection = x - waiter.getX(), y - waiter.getY() @@ -172,13 +195,14 @@ class WaiterManager(threading.Thread): if len(waiter.getAcceptedOrders()) > 1: waiter.set_target('kitchen') - path = self.__getNearestTargetPath(waiter, target=waiter.get_target()) + path = self.__getNearestTargetPath(waiter, [(14, 1), (13, 0)]) waiter.make_busy() else: if waiter.get_target() == 'return_order' and waiter.isPathEmpty(): print("Order returned") table = self._table_manager.get_table(waiter.get_remaining_positions()[0]) table.setStatus(Status.Served) + self.__changeWaiterDirection(waiter, table.getX(), table.getY()) waiter.get_remaining_positions().pop(0) time.sleep(2) @@ -187,7 +211,7 @@ class WaiterManager(threading.Thread): path = self.get_specified_path(waiter, waiter.get_remaining_positions()[0]) else: waiter.set_target('table') - path = self.__getNearestTargetPath(waiter, target='table') + path = self.__getNearestTargetPath(waiter) waiter.setPath([] if path is None else path) @@ -201,6 +225,7 @@ class WaiterManager(threading.Thread): if waiter.get_target() == 'kitchen': self._table_manager.pause() kitchen = self.__drawableManager.get_kitchen() + self.__changeWaiterDirection(waiter, kitchen.getX(), kitchen.getY()) waiter_orders = waiter.getAcceptedOrders() # print("Waiter near kitchen. Collected orders: {}".format(waiter_orders)) kitchen.add_orders(waiter_orders) diff --git a/Raport - Podprojekt Adam Toppmayer.pdf b/raports/Raport - Podprojekt Adam Toppmayer.pdf similarity index 100% rename from Raport - Podprojekt Adam Toppmayer.pdf rename to raports/Raport - Podprojekt Adam Toppmayer.pdf diff --git a/Raport - Podprojekt_Adam_Wojdyla.pdf b/raports/Raport - Podprojekt_Adam_Wojdyla.pdf similarity index 100% rename from Raport - Podprojekt_Adam_Wojdyla.pdf rename to raports/Raport - Podprojekt_Adam_Wojdyla.pdf diff --git a/Raport - _Automatyczny kelner_.pdf b/raports/Raport - _Automatyczny kelner_.pdf similarity index 100% rename from Raport - _Automatyczny kelner_.pdf rename to raports/Raport - _Automatyczny kelner_.pdf diff --git a/raports/Raport - algorytm genetyczny - generator stolików - Mikołaj Krzymiński.pdf b/raports/Raport - algorytm genetyczny - generator stolików - Mikołaj Krzymiński.pdf new file mode 100644 index 0000000..1909fe6 Binary files /dev/null and b/raports/Raport - algorytm genetyczny - generator stolików - Mikołaj Krzymiński.pdf differ diff --git a/Raport 2 - _Automatyczny kelner_.pdf b/raports/Raport 2 - _Automatyczny kelner_.pdf similarity index 100% rename from Raport 2 - _Automatyczny kelner_.pdf rename to raports/Raport 2 - _Automatyczny kelner_.pdf