diff --git a/.idea/ProjektAI.iml b/.idea/ProjektAI.iml index 9c88284..341f107 100644 --- a/.idea/ProjektAI.iml +++ b/.idea/ProjektAI.iml @@ -2,7 +2,7 @@ - + diff --git a/.idea/misc.xml b/.idea/misc.xml index ef004d1..8e241c5 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -3,4 +3,5 @@ + \ No newline at end of file diff --git a/kelner/images/kelner.png b/kelner/images/kelner.png index 6e0d499..c4bbba7 100644 Binary files a/kelner/images/kelner.png and b/kelner/images/kelner.png differ diff --git a/kelner/images/plate.png b/kelner/images/plate.png index 6f5173c..fbc97cf 100644 Binary files a/kelner/images/plate.png and b/kelner/images/plate.png differ diff --git a/kelner/images/wiking_blond.png b/kelner/images/wiking_blond.png index ebc4b63..9d84997 100644 Binary files a/kelner/images/wiking_blond.png and b/kelner/images/wiking_blond.png differ diff --git a/kelner/images/wiking_rudy.png b/kelner/images/wiking_rudy.png index 0c2e26c..6987964 100644 Binary files a/kelner/images/wiking_rudy.png and b/kelner/images/wiking_rudy.png differ diff --git a/kelner/images/wiking_rudy2.png b/kelner/images/wiking_rudy2.png index 702e5e8..63130b7 100644 Binary files a/kelner/images/wiking_rudy2.png and b/kelner/images/wiking_rudy2.png differ diff --git a/kelner/main.py b/kelner/main.py index 255061b..3377805 100644 --- a/kelner/main.py +++ b/kelner/main.py @@ -1,60 +1,64 @@ import pygame -from src.components.GridBoard import GridBoard -from src.managers.DrawableCollection import DrawableCollection -from src.managers.MenuManager import MenuManager -from src.components.Waiter import Waiter -from src.components.Table import Table -from src.managers.TaskManager import TaskManager +from kelner.src.components.GridBoard import GridBoard +from kelner.src.components.Waiter import Waiter +from kelner.src.components.Table import Table +from kelner.src.managers.DrawableCollection import DrawableCollection +from kelner.src.managers.MenuManager import MenuManager +from kelner.src.managers.TableManager import TableManager +from kelner.src.managers.WaiterManager import WaiterManager -#create screen consts -CellSize = 100 #pixel size of 1 square cell in the grid -GridCountX = 15 #number of columns in grid -GridCountY = 9 #number of rows in grid -ScreenWidth = CellSize * GridCountX #screen width in pixels -ScreenHeight = CellSize * GridCountY #screen height in pixels -Offset = 50 +# 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 +ScreenHeight = CellSize * GridCountY + 2 * PaintOffset # screen height in pixels -#initialize background -gridBoard = GridBoard(ScreenWidth+200, ScreenHeight+200, CellSize) +# initialize background +gridBoard = GridBoard(ScreenWidth, ScreenHeight) -#initialize drawable objects manager +# initialize drawable objects manager drawableManager = DrawableCollection() -#initialize menu manager +# initialize menu manager menuManager = MenuManager() -#initialize waiter component -waiter = Waiter(0, 0, 0, GridCountX - 1, 0, GridCountY - 1, CellSize) +# initialize waiter component +waiter = Waiter(0, 0, 0, GridCountX - 1, 0, GridCountY - 1, CellSize, PaintOffset) -#adds waiter to drawable collection +# adds waiter to drawable collection drawableManager.add(waiter) -#initialize a number of tables given in range -for i in range(1, 20): - table = Table(0, GridCountX - 1, 0, GridCountY - 1, CellSize) +# initialize a number of tables given in range +for i in range(1, 45): + table = Table(0, GridCountX - 1, 0, GridCountY - 1, CellSize, PaintOffset) drawableManager.generatePosition(table) drawableManager.add(table) -#main loop +# new thread controlling tables +tableTask = TableManager(drawableManager, menuManager) +tableTask.start() -#object that controlls repainting of changed objects -doRepaint = [True] - -#new thread -task = TaskManager(drawableManager, menuManager, doRepaint) -task.start() +# new thread controlling waiter +waiterTask = WaiterManager(drawableManager) +waiterTask.start() +# main loop running = True while running: for event in pygame.event.get(): if event.type == pygame.QUIT: + tableTask.stop() + waiterTask.stop() running = False - #handles keyboard events + # 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 + # checks if new waiter's position to the left is not occupied by other object if drawableManager.isPositionAvailable(waiter.getX() - 1, waiter.getY()): waiter.moveLeft() if event.key == pygame.K_RIGHT: @@ -69,13 +73,11 @@ while running: # checks if new waiter's position down is not occupied by other object if drawableManager.isPositionAvailable(waiter.getX(), waiter.getY() + 1): waiter.moveDown() - doRepaint[0] = True + drawableManager.forceRepaint() # repaints all objects to the screen - # is set only on initial paint or after keyboard event - if doRepaint[0]: + # is set only on initial paint or after keyboard event or call to forceRepaint() + if drawableManager.mustRepaint(): gridBoard.reinitialize() gridBoard.draw(drawableManager) gridBoard.udpdate() - doRepaint[0] = False - drawableManager.collectOrders() \ No newline at end of file diff --git a/kelner/src/algorithms/AStar/Finder.py b/kelner/src/algorithms/AStar/Finder.py new file mode 100644 index 0000000..4174ffa --- /dev/null +++ b/kelner/src/algorithms/AStar/Finder.py @@ -0,0 +1,125 @@ +from queue import PriorityQueue +from kelner.src.algorithms.AStar.Node import Node + + +class Finder: + + def __init__(self, table): + self._table = table + self._xMin = 0 + self._yMin = 0 + self._xMax = len(table[0]) - 1 + self._yMax = len(table) - 1 + self.__isDiagonal = False + + def _set(self, x, y, v): + self._table[y][x] = v + + def _get(self, x, y): + return self._table[y][x] + + # returns right position relative to the node + def __incXnopY(self, node, neighbours): + if node.x < self._xMax and self._get(node.x + 1, node.y) == 0: + neighbours.append(Node(node, node.x + 1, node.y, node.distance + 1)) + + # returns left position relative to the node + def __decXnopY(self, node, neighbours): + if self._xMin < node.x and self._get(node.x - 1, node.y) == 0: + neighbours.append(Node(node, node.x - 1, node.y, node.distance + 1)) + + # returns top position relative to the node + def __nopXincY(self, node, neighbours): + if node.y < self._yMax and self._get(node.x, node.y + 1) == 0: + neighbours.append(Node(node, node.x, node.y + 1, node.distance + 1)) + + # returns bottom position relative to the node + def __nopXdecY(self, node, neighbours): + if self._yMin < node.y and self._get(node.x, node.y - 1) == 0: + neighbours.append(Node(node, node.x, node.y - 1, node.distance + 1)) + + # returns left top position relative to the node + def __decXdecY(self, node, neighbours): + if (self._xMin < node.x and self._yMin < node.y and + self._get(node.x - 1, node.y - 1) == 0 and + self._get(node.x - 1, node.y) == 0 and + self._get(node.x, node.y - 1) == 0): + neighbours.append(Node(node, node.x - 1, node.y - 1, node.distance + 2)) + + # returns left bottom position relative to the node + def __decXincY(self, node, neighbours): + if (self._xMin < node.x and node.y < self._yMax and + self._get(node.x - 1, node.y + 1) == 0 and + self._get(node.x - 1, node.y) == 0 and + self._get(node.x, node.y + 1) == 0): + neighbours.append(Node(node, node.x - 1, node.y + 1, node.distance + 2)) + + # returns right bottom position relative to the node + def __incXincY(self, node, neighbours): + if (node.x < self._xMax and node.y < self._yMax and + self._get(node.x + 1, node.y + 1) == 0 and + self._get(node.x + 1, node.y) == 0 and + self._get(node.x, node.y + 1) == 0): + neighbours.append(Node(node, node.x + 1, node.y + 1, node.distance + 2)) + + # returns right top position relative to the node + def __incXdecY(self, node, neighbours): + if (node.x < self._xMax and self._yMin < node.y and + self._get(node.x + 1, node.y - 1) == 0 and + self._get(node.x + 1, node.y) == 0 and + self._get(node.x, node.y - 1) == 0): + neighbours.append(Node(node, node.x + 1, node.y - 1, node.distance + 2)) + + # returns all plausible positions relative to the node + def __getNeighbours(self, node): + neighbours = [] + self.__nopXincY(node, neighbours) + self.__incXnopY(node, neighbours) + self.__decXnopY(node, neighbours) + self.__nopXdecY(node, neighbours) + if self.__isDiagonal: + self.__decXdecY(node, neighbours) + self.__decXincY(node, neighbours) + self.__incXincY(node, neighbours) + self.__incXdecY(node, neighbours) + return neighbours + + # main algorithm - simplification of well known A* + def __getPath(self, origin, target): + Q = PriorityQueue() + V = set() + Q.put(origin) + while not Q.empty(): + head = Q.get() + if head == target: + return head + V.add(head) + for node in self.__getNeighbours(head): + if node not in V: + node.estimated = node.distance + node.getDistanceTo(target) + Q.put(node) + V.add(node) + return None + + # returns neighbours for locationXY-tuple as list of tuple(x,y) + def getNeighbours(self, locationXY, isDiagonal): + neighboursXY = [] + self.__isDiagonal = isDiagonal + location = Node(None, locationXY[0], locationXY[1], 0) + neighbours = self.__getNeighbours(location) + for neighbour in neighbours: + neighboursXY.append((neighbour.x, neighbour.y)) + return neighboursXY + + # returns the shortest path as list of tuple(x,y) from originXY-tuple to targetXY-tuple + def getPath(self, originXY, targetXY, isDiagonal): + self.__isDiagonal = isDiagonal + origin = Node(None, originXY[0], originXY[1], 0) + target = Node(None, targetXY[0], targetXY[1], 0) + result = self.__getPath(origin, target) + path = [] + while result is not None: + if result.parent is not None: + path.insert(0, (result.x, result.y)) + result = result.parent + return path diff --git a/kelner/src/algorithms/AStar/FinderTest.py b/kelner/src/algorithms/AStar/FinderTest.py new file mode 100644 index 0000000..e9a6946 --- /dev/null +++ b/kelner/src/algorithms/AStar/FinderTest.py @@ -0,0 +1,60 @@ +from random import randint +from kelner.src.algorithms.AStar.Finder import Finder + + +class FinderTest(Finder): + + def __init__(self, table): + super().__init__(table) + + def __setValues(self, xys, v): + if xys is not None: + for xy in xys: + self._set(xy[0], xy[1], v) + + def print(self, xys): + self.__setValues(xys, 2) + for row in self._table: + for col in row: + v = ' ' if col == 0 else '#' if col == 1 else 'O' + print('|', v, sep='', end='') + print('|') + self.__setValues(xys, 0) + + def getRandomTuple(self): + while True: + x = randint(self._xMin, self._xMax) + y = randint(self._yMin, self._yMax) + if self._get(x, y) == 0: + break + return x, y + + def getRandomBorderTuple(self): + xSet = [self._xMin, self._xMax] + ySet = [self._yMin, self._yMax] + while True: + x = randint(self._xMin, self._xMax) + y = randint(self._yMin, self._yMax) + if (x in xSet or y in ySet) and self._get(x, y) == 0: + break + return x, y + + def fillRandom(self): + for _ in range(120): + while True: + x = randint(self._xMin, self._xMax) + y = randint(self._yMin, self._yMax) + if self._get(x, y) == 0: + break + self._set(x, y, 1) + + +cols = 20 +rows = 20 +table = [[0] * cols for i in range(rows)] +finder = FinderTest(table) +finder.fillRandom() +originXY = finder.getRandomBorderTuple() +targetXY = finder.getRandomBorderTuple() +result = finder.getPath(originXY, targetXY, True) +finder.print(result) diff --git a/kelner/src/algorithms/AStar/Node.py b/kelner/src/algorithms/AStar/Node.py new file mode 100644 index 0000000..fff8edd --- /dev/null +++ b/kelner/src/algorithms/AStar/Node.py @@ -0,0 +1,34 @@ +from math import sqrt + + +class Node: + + def __init__(self, parent, x, y, distance): + self.parent = parent + self.x = x + self.y = y + self.distance = distance + self.estimated = distance + + # returns distance from the object to other node + def getDistanceTo(self, other): + dx = self.x - other.x + dy = self.y - other.y + return sqrt(dx * dx + dy * dy) + # abs(dx) + abs(dy) + + # used by str() method to represent the object + def __repr__(self): + return "%s:%s" % (self.x, self.y) + + # generates hash key for Set + def __hash__(self): + return hash(str(self)) + + # operator (==) for Set (determines if the object equals other node) + def __eq__(self, other): + return (self.x == other.x) and (self.y == other.y) + + # operator (>) for PriorityQueue comparison (determines the objects order) + def __gt__(self, other): + return self.estimated > other.estimated diff --git a/kelner/src/components/Drawable.py b/kelner/src/components/Drawable.py index 6af5eb9..6fd347b 100644 --- a/kelner/src/components/Drawable.py +++ b/kelner/src/components/Drawable.py @@ -5,29 +5,33 @@ class Drawable: RED = (255, 0, 0) GREEN = (0, 255, 0) - def __init__(self, x, y, minX, maxX, minY, maxY, ratio): + def __init__(self, x, y, minX, maxX, minY, maxY, cellSize, offset): self.__minX = minX self.__maxX = maxX self.__minY = minY self.__maxY = maxY self.setX(x) self.setY(y) - self.__ratio = ratio #cell size + self.__cellSize = cellSize # cell size in pixels + self.__offset = offset # paint offset in pixels def setX(self, x): - if (x < self.__minX or self.__maxX < x): + if x < self.__minX or self.__maxX < x: return False else: self.__x = x return True def setY(self, y): - if (y < self.__minY or self.__maxY < y): + if y < self.__minY or self.__maxY < y: return False else: self.__y = y return True + def isPositionCorrect(self, x, y): + return self.__minX <= x <= self.__maxX and self.__minY <= y <= self.__maxY + def getX(self): return self.__x @@ -46,8 +50,14 @@ class Drawable: def getMaxY(self): return self.__maxY - def getRatio(self): - return self.__ratio + def getCellSize(self): + return self.__cellSize + + def getOffset(self): + return self.__offset def draw(self, screen): - pass \ No newline at end of file + pass + + def drawAux(self, screen): + pass diff --git a/kelner/src/components/GridBoard.py b/kelner/src/components/GridBoard.py index 8f848fc..334aa9c 100644 --- a/kelner/src/components/GridBoard.py +++ b/kelner/src/components/GridBoard.py @@ -1,28 +1,32 @@ import pygame +from kelner.src.managers.ImageCache import ImageCache, Images + class GridBoard: - def __init__(self, width, height, cellSize): - pygame.init() #initialize the pygame - pygame.display.set_caption("Bardzo mądry kelner") #window caption + def __init__(self, width, height): + pygame.init() # initialize the pygame + pygame.display.set_caption("Bardzo mądry kelner") # window caption self.__width = width self.__height = height - self.__cellSize = cellSize self.__screen = pygame.display.set_mode((width, height)) # initialize screen - self.Offset = 50 - #fills the screen with white and draws grid + # draws the background def reinitialize(self): + imageBackground = ImageCache.getInstance().getImage(Images.Background, self.__width, self.__height) + self.__screen.blit(imageBackground, (0, 0)) + """ # code below fills the screen with white and draws grid self.__screen.fill((255, 255, 255)) for x in range(0, self.__width, self.__cellSize): pygame.draw.line(self.__screen, (0,0,0), (x,0), (x,(self.__height - 1))) for y in range(0, self.__height, self.__cellSize): pygame.draw.line(self.__screen, (0,0,0), (0,y), ((self.__width - 1),y)) + """ - #draws object on screen + # draws object on screen def draw(self, component): component.draw(self.__screen) - #updates screen + # updates screen def udpdate(self): - pygame.display.update() \ No newline at end of file + pygame.display.update() diff --git a/kelner/src/components/Table.py b/kelner/src/components/Table.py index 0b6de38..da5d328 100644 --- a/kelner/src/components/Table.py +++ b/kelner/src/components/Table.py @@ -1,79 +1,33 @@ -import pygame -from enum import Enum -from .Drawable import Drawable import random +from enum import Enum +from kelner.src.components.Drawable import Drawable +from kelner.src.managers.ImageCache import ImageCache, Images + + +# status of the table +class Status(Enum): + NotReady = 0 + Ready = 1 + Waiting = 2 + Served = 3 + class Table(Drawable): - def __init__(self, minX, maxX, minY, maxY, ratio): - #call base class constructor - Drawable.__init__(self, 0, 0, minX, maxX, minY, maxY, ratio) - self.__order = [] + def __init__(self, minX, maxX, minY, maxY, ratio, offset): + # call base class constructor + super().__init__(0, 0, minX, maxX, minY, maxY, ratio, offset) self.__status = Status.NotReady - self.ilosc_klientow = random.randint(1,3) - self.guest1 = self.getGuest() - self.guest2 = self.getGuest() - self.guest3 = self.getGuest() - self.ksiazka = self.__loadImg('./images/ksiazka.png') - self.stol = self.__loadImg('./images/stol.png') - self.check = self.__loadImg('./images/check.png') - self.plate = self.__loadImg('./images/plate.png') - self.Offset = 100 - - #sets table color based on it's status - def getColor(self): - color = None - if self.__status == Status.NotReady: - color = self.__loadImg('./images/stol.png') - elif self.__status == Status.Ready: - color = self.__loadImg('./images/kelner.png') - elif self.__status == Status.Waiting: - color = self.__loadImg('./images/kelner.png') - elif self.__status == Status.Served: - color = self.__loadImg('./images/kelner.png') - return color - - def getGuest(self): - guest = None - i = random.randint(1,3) - if i == 1: - guest = self.__loadImg('./images/wiking_blond.png') - elif i == 2: - guest = self.__loadImg('./images/wiking_rudy.png') - elif i == 3: - guest = self.__loadImg('./images/wiking_rudy2.png') - return guest - - def __loadImg(self, filePath): - return pygame.transform.scale((pygame.image.load(filePath)),(140,140)) - - def draw(self, screen): - screen.blit(self.stol, (self.getX() * 100-20+self.Offset, self.getY() * 100-20+self.Offset)) - if self.ilosc_klientow == 1: - screen.blit(self.guest1, (self.getX() * 100 - 20+self.Offset, self.getY() * 100 - 60+self.Offset)) - elif self.ilosc_klientow == 2: - screen.blit(self.guest1, (self.getX() * 100 - 62+self.Offset, self.getY() * 100 - 60+self.Offset)) - screen.blit(self.guest2, (self.getX() * 100 - 20+self.Offset, self.getY() * 100 - 60+self.Offset)) - elif self.ilosc_klientow == 3: - screen.blit(self.guest1, (self.getX() * 100 - 62+self.Offset, self.getY() * 100 - 60+self.Offset)) - screen.blit(self.guest2, (self.getX() * 100 - 20+self.Offset, self.getY() * 100 - 60+self.Offset)) - screen.blit(self.guest3, (self.getX() * 100 + 22+self.Offset, self.getY() * 100 - 60+self.Offset)) - - if self.__status == Status.NotReady: - screen.blit(self.ksiazka, (self.getX() * 100 - 20+self.Offset, self.getY() * 100 - 20+self.Offset)) - elif self.__status == Status.Ready: - screen.blit(self.check, (self.getX() * 100 - 20+self.Offset, self.getY() * 100 - 20+self.Offset)) - elif self.__status == Status.Waiting: - if self.ilosc_klientow == 1: - screen.blit(self.plate, (self.getX() * 100 - 20+self.Offset, self.getY() * 100 - 20+self.Offset)) - elif self.ilosc_klientow == 2: - screen.blit(self.plate, (self.getX() * 100 - 62+self.Offset, self.getY() * 100 - 20+self.Offset)) - screen.blit(self.plate, (self.getX() * 100 - 20+self.Offset, self.getY() * 100 - 20+self.Offset)) - elif self.ilosc_klientow == 3: - screen.blit(self.plate, (self.getX() * 100 - 62+self.Offset, self.getY() * 100 - 20+self.Offset)) - screen.blit(self.plate, (self.getX() * 100- 20+self.Offset, self.getY() * 100 - 20+self.Offset)) - screen.blit(self.plate, (self.getX() * 100+ 22+self.Offset, self.getY() * 100 - 20+self.Offset)) + self.__order = [] + self.__guests = self.__getRandomGuests() + def __getRandomGuests(self): + possibleGuests = [Images.Guest1, Images.Guest2, Images.Guest3] + guests = [] + guestCount = random.randint(1, len(possibleGuests)) + for _ in range(guestCount): + guests.insert(0, possibleGuests[random.randint(0, len(possibleGuests) - 1)]) + return guests def setOrder(self, order): self.__order = order @@ -90,9 +44,57 @@ class Table(Drawable): def setStatus(self, status): self.__status = status -#status of the table -class Status(Enum): - NotReady = 0 - Ready = 1 - Waiting = 2 - Served = 3 \ No newline at end of file + def __getImage(self, imageKind): + if imageKind in [Images.Guest1, Images.Guest2, Images.Guest3, Images.Plate]: + size = int(self.getCellSize() / 3) + else: + size = int(1.4 * self.getCellSize()) + return ImageCache.getInstance().getImage(imageKind, size, size) + + # draws only table + def draw(self, screen): + xBase = self.getX() * self.getCellSize() + self.getOffset() + yBase = self.getY() * self.getCellSize() + self.getOffset() + tableXYOffset = int(0.2 * self.getCellSize()) + screen.blit(self.__getImage(Images.Table), (xBase - tableXYOffset, yBase - tableXYOffset)) + + # draws images related to the status of a table + # the method is called in the second turn when all tables are already painted + def drawAux(self, screen): + xBase = self.getX() * self.getCellSize() + self.getOffset() + yBase = self.getY() * self.getCellSize() + self.getOffset() + + guest1XOffset = 0 + guest2XOffset = int((1 / 3) * self.getCellSize()) + guest3XOffset = int((2 / 3) * self.getCellSize()) + guest4XOffset = int((1 / 9) * self.getCellSize()) + guest5XOffset = int((5 / 9) * self.getCellSize()) + guestsYOffset = int(0.1 * self.getCellSize()) + tableXYOffset = int(0.2 * self.getCellSize()) + + if len(self.__guests) == 1: + screen.blit(self.__getImage(self.__guests[0]), (xBase + guest2XOffset, yBase - guestsYOffset)) + elif len(self.__guests) == 2: + screen.blit(self.__getImage(self.__guests[0]), (xBase + guest4XOffset, yBase - guestsYOffset)) + screen.blit(self.__getImage(self.__guests[1]), (xBase + guest5XOffset, yBase - guestsYOffset)) + elif len(self.__guests) == 3: + screen.blit(self.__getImage(self.__guests[0]), (xBase + guest1XOffset, yBase - guestsYOffset)) + screen.blit(self.__getImage(self.__guests[1]), (xBase + guest2XOffset, yBase - guestsYOffset)) + screen.blit(self.__getImage(self.__guests[2]), (xBase + guest3XOffset, yBase - guestsYOffset)) + + if self.isStatus(Status.NotReady): + screen.blit(self.__getImage(Images.Menu), (xBase - tableXYOffset, yBase - tableXYOffset)) + elif self.isStatus(Status.Ready): + screen.blit(self.__getImage(Images.Check), (xBase - tableXYOffset, yBase - tableXYOffset)) + elif self.isStatus(Status.Waiting): + platesYOffset = int(0.3 * self.getCellSize()) + imagePlate = self.__getImage(Images.Plate) + if len(self.__guests) == 1: + screen.blit(imagePlate, (xBase + guest2XOffset, yBase + platesYOffset)) + elif len(self.__guests) == 2: + screen.blit(imagePlate, (xBase + guest4XOffset, yBase + platesYOffset)) + screen.blit(imagePlate, (xBase + guest5XOffset, yBase + platesYOffset)) + elif len(self.__guests) == 3: + screen.blit(imagePlate, (xBase + guest1XOffset, yBase + platesYOffset)) + screen.blit(imagePlate, (xBase + guest2XOffset, yBase + platesYOffset)) + screen.blit(imagePlate, (xBase + guest3XOffset, yBase + platesYOffset)) diff --git a/kelner/src/components/Waiter.py b/kelner/src/components/Waiter.py index 9b64ed0..1f57b71 100644 --- a/kelner/src/components/Waiter.py +++ b/kelner/src/components/Waiter.py @@ -1,15 +1,14 @@ -import pygame -from .Drawable import Drawable +from kelner.src.components.Drawable import Drawable +from kelner.src.managers.ImageCache import ImageCache, Images + class Waiter(Drawable): - def __init__(self, x, y, minX, maxX, minY, maxY, ratio): - #call base class constructor - Drawable.__init__(self, x, y, minX, maxX, minY, maxY, ratio) - self.__image = self.__loadImg('./images/kelner.png') + def __init__(self, x, y, minX, maxX, minY, maxY, ratio, offset): + # call base class constructor + super().__init__(x, y, minX, maxX, minY, maxY, ratio, offset) self.__acceptedOrders = [] - self.Offset = 100 - + self.__currentPath = [] def moveUp(self): if self.getY() > self.getMinY(): @@ -39,12 +38,21 @@ class Waiter(Drawable): else: return False - #accepts orders from the table and stores them in queue + # accepts orders from the table and stores them in queue def addOrder(self, table): self.__acceptedOrders += [(table, table.getOrder())] - def __loadImg(self, filePath): - return pygame.transform.scale((pygame.image.load(filePath)),(140,140)) + def isPathEmpty(self): + return self.__currentPath == [] + + def setPath(self, path): + self.__currentPath = path + + def popStepFromPath(self): + return self.__currentPath.pop(0) def draw(self, screen): - screen.blit(self.__image, (self.getX()*100-20+self.Offset, self.getY()*100-20+self.Offset)) + imageWaiter = ImageCache.getInstance().getImage(Images.Waiter, self.getCellSize(), self.getCellSize()) + xBase = self.getX() * self.getCellSize() + self.getOffset() + yBase = self.getY() * self.getCellSize() + self.getOffset() + screen.blit(imageWaiter, (xBase, yBase)) diff --git a/kelner/src/managers/DrawableCollection.py b/kelner/src/managers/DrawableCollection.py index 9848c68..cb17915 100644 --- a/kelner/src/managers/DrawableCollection.py +++ b/kelner/src/managers/DrawableCollection.py @@ -1,22 +1,24 @@ import random -from src.components.Table import Table, Status -from src.components.Waiter import Waiter -import pygame -#drawable objects manager +from kelner.src.components.Table import Table, Status +from kelner.src.components.Waiter import Waiter + + +# drawable objects manager class DrawableCollection: - #const, minimal distance between objects - __MinDistance = 0 + # const, minimal distance between objects + __MinDistanceX = 0 + __MinDistanceY = 0 def __init__(self): - #collection that holds all drawable objects + # collection that holds all drawable objects + self.__mustRepaint = True self.__drawables = [] - self.image = pygame.transform.scale((pygame.image.load('./images/Backgroud.png')),(1700,1100)) - #adds drawable objects to the collection + # adds drawable objects to the collection def add(self, drawable): self.__drawables.append(drawable) - #generates and sets random (x, y) and cheks if it's not occupied by other object + # generates and sets random (x, y) and cheks if it's not occupied by other object def generatePosition(self, drawable): isPositionUnique = False while not isPositionUnique: @@ -24,20 +26,14 @@ class DrawableCollection: y = random.randint(drawable.getMinY() + 1, drawable.getMaxY() - 1) isPositionUnique = True for item in self.__drawables: - if abs(item.getX() - x) <= self.__MinDistance and abs(item.getY() - y) <= self.__MinDistance: + if abs(item.getX() - x) <= self.__MinDistanceX and abs(item.getY() - y) <= self.__MinDistanceY: isPositionUnique = False break if isPositionUnique: drawable.setX(x) drawable.setY(y) - #draws all objects stored in collection - def draw(self, screen): - screen.blit(self.image, (0, 0)) - for item in self.__drawables: - item.draw(screen) - - #checks if position (x,y) is not occupied by other object + # checks if position (x,y) is not occupied by other object def isPositionAvailable(self, x, y): isPositionAvailable = True for item in self.__drawables: @@ -46,29 +42,56 @@ class DrawableCollection: break return isPositionAvailable - #gets all tables by status from collection + # gets all tables by status from collection def getTables(self, status): result = [] for item in self.__drawables: - if isinstance(item, Table) and item.isStatus(status): + if status is None or isinstance(item, Table) and item.isStatus(status): result += [item] return result - #gets all waiters from collection - def getWaites(self): + # gets all waiters from collection + def getWaiters(self): result = [] for item in self.__drawables: if isinstance(item, Waiter): result += [item] return result - #waiter collects order from the nearest table + # waiter collects orders from all nearest tables def collectOrders(self): - waiters = self.getWaites() + waiters = self.getWaiters() for waiter in waiters: tables = self.getTables(Status.Ready) 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): table.setStatus(Status.Waiting) waiter.addOrder(table) - table.delOrder() \ No newline at end of file + table.delOrder() + + # returns table: 0 - position is available, 1 - position is occupied + def getReservedPlaces(self, waiter): + cols = waiter.getMaxX() - waiter.getMinX() + 1 + rows = waiter.getMaxY() - waiter.getMinY() + 1 + reservedPlaces = [[0] * cols for i in range(rows)] + tables = self.getTables(None) + if tables: + for table in tables: + reservedPlaces[table.getY()][table.getX()] = 1 + return reservedPlaces + + # the method is called externally and forces repainting + def forceRepaint(self): + self.__mustRepaint = True + + # returns boolean value: True if objects should be repainted otherwise False + def mustRepaint(self): + return self.__mustRepaint + + # draws all objects stored in collection + def draw(self, screen): + for item in self.__drawables: + item.draw(screen) + for item in self.__drawables: + item.drawAux(screen) + self.__mustRepaint = False diff --git a/kelner/src/managers/ImageCache.py b/kelner/src/managers/ImageCache.py new file mode 100644 index 0000000..4fa85f8 --- /dev/null +++ b/kelner/src/managers/ImageCache.py @@ -0,0 +1,50 @@ +import pygame +from enum import Enum + + +# images enum +class Images(Enum): + Background = 0 + Waiter = 1 + Table = 2 + Menu = 3 + Check = 4 + Plate = 5 + Guest1 = 6 + Guest2 = 7 + Guest3 = 8 + + +class ImageCache: + __instance = None + + @staticmethod + def getInstance(): + if ImageCache.__instance is None: + ImageCache() + return ImageCache.__instance + + def __init__(self): + """ Virtually private constructor. """ + if ImageCache.__instance is not None: + raise Exception("This class is a singleton!") + else: + ImageCache.__instance = self + self.__images = {} + self.__paths = {Images.Background: './images/Backgroud.png', + Images.Waiter: './images/kelner.png', + Images.Table: './images/stol.png', + Images.Menu: './images/ksiazka.png', + Images.Check: './images/check.png', + Images.Plate: './images/plate.png', + Images.Guest1: './images/wiking_blond.png', + Images.Guest2: './images/wiking_rudy.png', + Images.Guest3: './images/wiking_rudy2.png'} + + def getImage(self, imageKind, width, height): + key = str(imageKind.value) + ':' + str(width) + ':' + str(height) + image = self.__images.get(key, None) + if image is None: + image = pygame.transform.scale((pygame.image.load(self.__paths[imageKind])), (width, height)) + self.__images[key] = image + return image diff --git a/kelner/src/managers/MenuManager.py b/kelner/src/managers/MenuManager.py index 7b9222b..47b34d5 100644 --- a/kelner/src/managers/MenuManager.py +++ b/kelner/src/managers/MenuManager.py @@ -1,9 +1,10 @@ import random -#contains all dishes and generates random order for the table + +# contains all dishes and generates random order for the table class MenuManager: - #consts, min and max dishes oredered by the people sitting by the same table + # consts, min and max dishes ordered by the people sitting by the same table __MinDishes = 1 __MaxDishes = 3 @@ -24,10 +25,11 @@ class MenuManager: "EMPTY PLATE", "BEER", "CAKE"] - #generator + + # generator def generateOrder(self): count = random.randint(self.__MinDishes, self.__MaxDishes) order = [] for i in range(0, count): order += [(self.__menuCard[random.randint(0, len(self.__menuCard) - 1)])] - return order \ No newline at end of file + return order diff --git a/kelner/src/managers/TableManager.py b/kelner/src/managers/TableManager.py new file mode 100644 index 0000000..4ea524a --- /dev/null +++ b/kelner/src/managers/TableManager.py @@ -0,0 +1,29 @@ +import threading +import time +import random +from kelner.src.components.Table import Status + + +# creates new thread +class TableManager (threading.Thread): + + def __init__(self, drawableManager, menuManager): + super().__init__() + self.__drawableManager = drawableManager + self.__menuManager = menuManager + self.__runThread = True + + # changes the status of a random table from NotReady to Ready + def run(self): + while self.__runThread: + tables = self.__drawableManager.getTables(Status.NotReady) + if tables: + tableIndex = random.randint(0, len(tables) - 1) + table = tables[tableIndex] + time.sleep(3) + table.setStatus(Status.Ready) + table.setOrder(self.__menuManager.generateOrder()) + self.__drawableManager.forceRepaint() + + def stop(self): + self.__runThread = False diff --git a/kelner/src/managers/TaskManager.py b/kelner/src/managers/TaskManager.py deleted file mode 100644 index 686937c..0000000 --- a/kelner/src/managers/TaskManager.py +++ /dev/null @@ -1,25 +0,0 @@ -import threading -import time -import random -from src.components.Table import Status - -#creates new threads -class TaskManager (threading.Thread): - - def __init__(self, drawableManager, menuManager, doRepaintObject): - threading.Thread.__init__(self) - self.__drawableManager = drawableManager - self.__menuManager = menuManager - self.__doRepaintObject = doRepaintObject - - #changes the status of a random table from NotReady to Ready - def run(self): - while True: - time.sleep(3) - tables = self.__drawableManager.getTables(Status.NotReady) - if tables != []: - tableIndex = random.randint(0, len(tables) - 1) - table = tables[tableIndex] - table.setStatus(Status.Ready) - table.setOrder(self.__menuManager.generateOrder()) - self.__doRepaintObject[0] = True \ No newline at end of file diff --git a/kelner/src/managers/WaiterManager.py b/kelner/src/managers/WaiterManager.py new file mode 100644 index 0000000..2240637 --- /dev/null +++ b/kelner/src/managers/WaiterManager.py @@ -0,0 +1,57 @@ +import threading +import time +import sys +from kelner.src.components.Table import Status +from kelner.src.algorithms.AStar.Finder import Finder + + +# creates new thread +class WaiterManager (threading.Thread): + + def __init__(self, drawableManager): + super().__init__() + self.__drawableManager = drawableManager + self.__runThread = True + + def __getNearestTargetPath(self, waiter): + distance = sys.maxsize + nearestTargetPath = None + tables = self.__drawableManager.getTables(Status.Ready) + if tables: + reservedPlaces = self.__drawableManager.getReservedPlaces(waiter) + finder = Finder(reservedPlaces) + origin = (waiter.getX(), waiter.getY()) + for table in tables: + 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 + return nearestTargetPath + + # changes the status of a random table from NotReady to Ready + def run(self): + while self.__runThread: + waiters = self.__drawableManager.getWaiters() + if waiters: + for waiter in waiters: + path = self.__getNearestTargetPath(waiter) + if path is not None: + waiter.setPath(path) + if not waiter.isPathEmpty(): + step = waiter.popStepFromPath() + time.sleep(0.4) + waiter.setX(step[0]) + waiter.setY(step[1]) + self.__drawableManager.forceRepaint() + if waiter.isPathEmpty(): + time.sleep(2) + self.__drawableManager.collectOrders() + self.__drawableManager.forceRepaint() + + def stop(self): + self.__runThread = False