diff --git a/.idea/ProjektAI.iml b/.idea/ProjektAI.iml index 341f107..54ef975 100644 --- a/.idea/ProjektAI.iml +++ b/.idea/ProjektAI.iml @@ -2,7 +2,7 @@ - + diff --git a/.idea/misc.xml b/.idea/misc.xml index 8e241c5..ba00af5 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -3,5 +3,5 @@ - + \ No newline at end of file diff --git a/kelner/images/Untitleda.png b/kelner/images/Untitleda.png deleted file mode 100644 index f9f1991..0000000 Binary files a/kelner/images/Untitleda.png and /dev/null differ diff --git a/kelner/images/drive-download-20200405T181945Z-001.zip b/kelner/images/drive-download-20200405T181945Z-001.zip deleted file mode 100644 index 72d8b64..0000000 Binary files a/kelner/images/drive-download-20200405T181945Z-001.zip and /dev/null differ diff --git a/kelner/images/kelner_full_D.png b/kelner/images/kelner_full_D.png new file mode 100644 index 0000000..1d8011c Binary files /dev/null and b/kelner/images/kelner_full_D.png differ diff --git a/kelner/images/kelner_full_L.png b/kelner/images/kelner_full_L.png new file mode 100644 index 0000000..0290a59 Binary files /dev/null and b/kelner/images/kelner_full_L.png differ diff --git a/kelner/images/kelner_full_LD.png b/kelner/images/kelner_full_LD.png new file mode 100644 index 0000000..e75e597 Binary files /dev/null and b/kelner/images/kelner_full_LD.png differ diff --git a/kelner/images/kelner_full_LU.png b/kelner/images/kelner_full_LU.png new file mode 100644 index 0000000..0ce3b8c Binary files /dev/null and b/kelner/images/kelner_full_LU.png differ diff --git a/kelner/images/kelner_full_R.png b/kelner/images/kelner_full_R.png new file mode 100644 index 0000000..1a210ba Binary files /dev/null and b/kelner/images/kelner_full_R.png differ diff --git a/kelner/images/kelner_full_RD.png b/kelner/images/kelner_full_RD.png new file mode 100644 index 0000000..b1a56b4 Binary files /dev/null and b/kelner/images/kelner_full_RD.png differ diff --git a/kelner/images/kelner_full_RU.png b/kelner/images/kelner_full_RU.png new file mode 100644 index 0000000..509cc2b Binary files /dev/null and b/kelner/images/kelner_full_RU.png differ diff --git a/kelner/images/kelner_full_U.png b/kelner/images/kelner_full_U.png new file mode 100644 index 0000000..1d87f28 Binary files /dev/null and b/kelner/images/kelner_full_U.png differ diff --git a/kelner/images/srcWaiter.png b/kelner/images/srcWaiter.png deleted file mode 100644 index 512d036..0000000 Binary files a/kelner/images/srcWaiter.png and /dev/null differ diff --git a/kelner/images/tooltip.png b/kelner/images/tooltip.png new file mode 100644 index 0000000..069d4a8 Binary files /dev/null and b/kelner/images/tooltip.png differ diff --git a/kelner/images/waiter.png b/kelner/images/waiter.png deleted file mode 100644 index 67722bb..0000000 Binary files a/kelner/images/waiter.png and /dev/null differ diff --git a/kelner/main.py b/kelner/main.py index 3377805..604fa4f 100644 --- a/kelner/main.py +++ b/kelner/main.py @@ -26,24 +26,39 @@ drawableManager = DrawableCollection() menuManager = MenuManager() # initialize waiter component -waiter = Waiter(0, 0, 0, GridCountX - 1, 0, GridCountY - 1, CellSize, PaintOffset) +waiter1 = Waiter(0, 0, 0, GridCountX - 1, 0, GridCountY - 1, CellSize, PaintOffset) +waiter2 = Waiter(0, 1, 0, GridCountX - 1, 0, GridCountY - 1, CellSize, PaintOffset) +waiter3 = Waiter(0, 2, 0, GridCountX - 1, 0, GridCountY - 1, CellSize, PaintOffset) +waiter4 = Waiter(0, 3, 0, GridCountX - 1, 0, GridCountY - 1, CellSize, PaintOffset) # adds waiter to drawable collection -drawableManager.add(waiter) +drawableManager.add(waiter1) +drawableManager.add(waiter2) +drawableManager.add(waiter3) +drawableManager.add(waiter4) # initialize a number of tables given in range -for i in range(1, 45): +for i in range(1, 40): table = Table(0, GridCountX - 1, 0, GridCountY - 1, CellSize, PaintOffset) - drawableManager.generatePosition(table) - drawableManager.add(table) + if drawableManager.generatePosition(table): + drawableManager.add(table) # new thread controlling tables tableTask = TableManager(drawableManager, menuManager) tableTask.start() # new thread controlling waiter -waiterTask = WaiterManager(drawableManager) -waiterTask.start() +waiter1Task = WaiterManager(drawableManager, [waiter1]) +waiter1Task.start() + +waiter2Task = WaiterManager(drawableManager, [waiter2]) +waiter2Task.start() + +waiter3Task = WaiterManager(drawableManager, [waiter3]) +waiter3Task.start() + +waiter4Task = WaiterManager(drawableManager, [waiter4]) +waiter4Task.start() # main loop running = True @@ -52,27 +67,51 @@ while running: for event in pygame.event.get(): if event.type == pygame.QUIT: tableTask.stop() - waiterTask.stop() + waiter1Task.stop() + waiter2Task.stop() + waiter3Task.stop() + waiter4Task.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(waiter.getX() - 1, waiter.getY()): - waiter.moveLeft() + 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(waiter.getX() + 1, waiter.getY()): - waiter.moveRight() + 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(waiter.getX(), waiter.getY() - 1): - waiter.moveUp() + 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(waiter.getX(), waiter.getY() + 1): - waiter.moveDown() + 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 diff --git a/kelner/src/algorithms/AStar/Finder.py b/kelner/src/algorithms/AStar/Finder.py index 4174ffa..098ea16 100644 --- a/kelner/src/algorithms/AStar/Finder.py +++ b/kelner/src/algorithms/AStar/Finder.py @@ -1,125 +1,140 @@ -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 +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 + + 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, direction, isDiagonal, neighbours): + if node.x < self._xMax and self._get(node.x + 1, node.y) == 0: + turnsCount = self.getTurnsCount(direction, (1, 0), isDiagonal) + neighbours.append(Node(node, node.x + 1, node.y, node.distance + turnsCount + 1)) + + # returns left position relative to the node + def __decXnopY(self, node, direction, isDiagonal, neighbours): + if self._xMin < node.x and self._get(node.x - 1, node.y) == 0: + turnsCount = self.getTurnsCount(direction, (-1, 0), isDiagonal) + neighbours.append(Node(node, node.x - 1, node.y, node.distance + turnsCount + 1)) + + # returns top position relative to the node + def __nopXincY(self, node, direction, isDiagonal, neighbours): + if node.y < self._yMax and self._get(node.x, node.y + 1) == 0: + turnsCount = self.getTurnsCount(direction, (0, 1), isDiagonal) + neighbours.append(Node(node, node.x, node.y + 1, node.distance + turnsCount + 1)) + + # returns bottom position relative to the node + def __nopXdecY(self, node, direction, isDiagonal, neighbours): + if self._yMin < node.y and self._get(node.x, node.y - 1) == 0: + turnsCount = self.getTurnsCount(direction, (0, -1), isDiagonal) + neighbours.append(Node(node, node.x, node.y - 1, node.distance + turnsCount + 1)) + + # returns left top position relative to the node + def __decXdecY(self, node, direction, isDiagonal, 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): + turnsCount = self.getTurnsCount(direction, (-1, -1), isDiagonal) + neighbours.append(Node(node, node.x - 1, node.y - 1, node.distance + turnsCount + 2)) + + # returns left bottom position relative to the node + def __decXincY(self, node, direction, isDiagonal, 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): + turnsCount = self.getTurnsCount(direction, (-1, 1), isDiagonal) + neighbours.append(Node(node, node.x - 1, node.y + 1, node.distance + turnsCount + 2)) + + # returns right bottom position relative to the node + def __incXincY(self, node, direction, isDiagonal, 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): + turnsCount = self.getTurnsCount(direction, (1, 1), isDiagonal) + neighbours.append(Node(node, node.x + 1, node.y + 1, node.distance + turnsCount + 2)) + + # returns right top position relative to the node + def __incXdecY(self, node, direction, isDiagonal, 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): + turnsCount = self.getTurnsCount(direction, (1, -1), isDiagonal) + neighbours.append(Node(node, node.x + 1, node.y - 1, node.distance + turnsCount + 2)) + + # returns all plausible positions relative to the node + def __getNeighbours(self, node, isDiagonal): + direction = node.getDirection() + neighbours = [] + self.__nopXincY(node, direction, isDiagonal, neighbours) + self.__incXnopY(node, direction, isDiagonal, neighbours) + self.__decXnopY(node, direction, isDiagonal, neighbours) + self.__nopXdecY(node, direction, isDiagonal, neighbours) + if isDiagonal: + self.__decXdecY(node, direction, isDiagonal, neighbours) + self.__decXincY(node, direction, isDiagonal, neighbours) + self.__incXincY(node, direction, isDiagonal, neighbours) + self.__incXdecY(node, direction, isDiagonal, neighbours) + return neighbours + + # main algorithm - simplification of well known A* + def __getPath(self, origin, target, isDiagonal): + 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, isDiagonal): + if node not in V: + node.estimated = node.distance + node.getDistanceTo(target) + Q.put(node) + V.add(node) + return None + + # returns the number of turns to change direction from old to new + @staticmethod + def getTurnsCount(oldDirection, newDirection, isDiagonal): + if oldDirection == (0, 0) or oldDirection == newDirection: + return 0 + if 0 == oldDirection[0] + newDirection[0] and 0 == oldDirection[1] + newDirection[1]: + return 4 if isDiagonal else 2 + return abs(newDirection[0] - oldDirection[0]) + abs(newDirection[1] - oldDirection[1]) if isDiagonal else 1 + + # returns neighbours for locationXY-tuple as list of tuple(x,y) + def getNeighbours(self, locationXY, isDiagonal): + neighboursXY = [] + location = Node(None, locationXY[0], locationXY[1], 0) + neighbours = self.__getNeighbours(location, isDiagonal) + 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): + origin = Node(None, originXY[0], originXY[1], 0) + target = Node(None, targetXY[0], targetXY[1], 0) + result = self.__getPath(origin, target, isDiagonal) + 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 index e9a6946..945681a 100644 --- a/kelner/src/algorithms/AStar/FinderTest.py +++ b/kelner/src/algorithms/AStar/FinderTest.py @@ -1,60 +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) +import random +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 = random.randint(self._xMin, self._xMax) + y = random.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 = random.randint(self._xMin, self._xMax) + y = random.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 = random.randint(self._xMin, self._xMax) + y = random.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 index fff8edd..edc977b 100644 --- a/kelner/src/algorithms/AStar/Node.py +++ b/kelner/src/algorithms/AStar/Node.py @@ -1,34 +1,40 @@ -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 +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) + + def getDirection(self): + if self.parent is None: + return 0, 0 + else: + return self.x - self.parent.x, self.y - self.parent.y + + # 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 6fd347b..02d91d7 100644 --- a/kelner/src/components/Drawable.py +++ b/kelner/src/components/Drawable.py @@ -10,10 +10,10 @@ class Drawable: self.__maxX = maxX self.__minY = minY self.__maxY = maxY - self.setX(x) - self.setY(y) - self.__cellSize = cellSize # cell size in pixels - self.__offset = offset # paint offset in pixels + self.__x = x + self.__y = y + 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: diff --git a/kelner/src/components/Waiter.py b/kelner/src/components/Waiter.py index 1f57b71..e08788b 100644 --- a/kelner/src/components/Waiter.py +++ b/kelner/src/components/Waiter.py @@ -1,18 +1,35 @@ +import random + +import pygame + from kelner.src.components.Drawable import Drawable from kelner.src.managers.ImageCache import ImageCache, Images +class Direction: + LeftUp = (-1,-1) + Up = ( 0,-1) + RightUp = ( 1,-1) + Right = ( 1, 0) + RightDown = ( 1, 1) + Down = ( 0, 1) + LeftDown = (-1, 1) + Left = (-1, 0) + class Waiter(Drawable): 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.__dx = Direction.Down[0] + self.__dy = Direction.Down[1] self.__acceptedOrders = [] self.__currentPath = [] def moveUp(self): if self.getY() > self.getMinY(): self.setY(self.getY() - 1) + self.setX(self.getX()) return True else: return False @@ -20,6 +37,7 @@ class Waiter(Drawable): def moveDown(self): if self.getY() < self.getMaxY(): self.setY(self.getY() + 1) + self.setX(self.getX()) return True else: return False @@ -27,6 +45,7 @@ class Waiter(Drawable): def moveLeft(self): if self.getX() > self.getMinX(): self.setX(self.getX() - 1) + self.setY(self.getY()) return True else: return False @@ -34,10 +53,56 @@ class Waiter(Drawable): def moveRight(self): if self.getX() < self.getMaxX(): self.setX(self.getX() + 1) + self.setY(self.getY()) return True else: return False + def setX(self, x): + oldX = self.getX() + if super().setX(x): + self.__dx = x - oldX + return True + else: + return False + + def setY(self, y): + oldY = self.getY() + if super().setY(y): + self.__dy = y - oldY + return True + else: + return False + + def getDirection(self): + return self.__dx, self.__dy + + def setDirection(self, dx, dy): + self.__dx = dx + self.__dy = dy + + def getNextDirection(self, oldDirectionXY, newDirectionXY, isDiagonal): + if oldDirectionXY == (0, 0) or oldDirectionXY == newDirectionXY: + return None + if 0 == oldDirectionXY[0]: + if 0 == newDirectionXY[0]: + return random.choice([-1, 1]), oldDirectionXY[1] if isDiagonal else 0 + else: + dx = newDirectionXY[0] - oldDirectionXY[0] + return +1 if dx > 0 else -1, oldDirectionXY[1] if isDiagonal else 0 + if 0 == oldDirectionXY[1]: + if 0 == newDirectionXY[1]: + return oldDirectionXY[0] if isDiagonal else 0, random.choice([-1, 1]) + else: + dy = newDirectionXY[1] - oldDirectionXY[1] + return oldDirectionXY[0] if isDiagonal else 0, +1 if dy > 0 else -1 + if 0 == oldDirectionXY[0] + newDirectionXY[0] and 0 == oldDirectionXY[1] + newDirectionXY[1]: + return random.choice([(oldDirectionXY[0], 0), (0, oldDirectionXY[1])]) + else: + dx = newDirectionXY[0] - oldDirectionXY[0] + dy = newDirectionXY[1] - oldDirectionXY[1] + return (0, oldDirectionXY[1]) if abs(dx) > abs(dy) else (oldDirectionXY[0], 0) + # accepts orders from the table and stores them in queue def addOrder(self, table): self.__acceptedOrders += [(table, table.getOrder())] @@ -52,7 +117,47 @@ class Waiter(Drawable): return self.__currentPath.pop(0) def draw(self, screen): - 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)) + direction = self.getDirection() + imageKind = None + if direction == Direction.LeftUp: + imageKind = Images.WaiterLeftUp + elif direction == Direction.Up: + imageKind = Images.WaiterUp + elif direction == Direction.RightUp: + imageKind = Images.WaiterRightUp + elif direction == Direction.Right: + imageKind = Images.WaiterRight + elif direction == Direction.RightDown: + imageKind = Images.WaiterRightDown + elif direction == Direction.Down: + imageKind = Images.WaiterDown + elif direction == Direction.LeftDown: + imageKind = Images.WaiterLeftDown + elif direction == Direction.Left: + imageKind = Images.WaiterLeft + + imageWaiter = ImageCache.getInstance().getImage(imageKind, self.getCellSize(), self.getCellSize()) + self.__xBase = self.getX() * self.getCellSize() + self.getOffset() + self.__yBase = self.getY() * self.getCellSize() + self.getOffset() + screen.blit(imageWaiter, (self.__xBase, self.__yBase)) + + def drawAux(self, screen): + toolTipWidth = int(0.4 * self.getCellSize()) + toolTipHeight = int(0.2 * self.getCellSize()) + toolTipXOffset = int(0.6 * self.getCellSize()) + toolTipYOffset = - int(0.1 * self.getCellSize()) + + imageToolTip = ImageCache.getInstance().getImage(Images.ToolTip, toolTipWidth, toolTipHeight) + screen.blit(imageToolTip, (self.__xBase + toolTipXOffset, self.__yBase + toolTipYOffset)) + + font = pygame.font.SysFont('comicsansms', 24, True) + imageText = font.render(str(len(self.__acceptedOrders)), True, (204, 0, 0)) + size = imageText.get_size() + ratio = 0.9 * toolTipHeight + textWidth = int((ratio / size[1]) * size[0]) + textHeight = int(ratio) + imageText = pygame.transform.scale(imageText, (textWidth, textHeight)) + + textXOffset = toolTipXOffset + int((toolTipWidth - textWidth) / 2) + textYOffset = toolTipYOffset + int(0.05 * toolTipHeight) + screen.blit(imageText, (self.__xBase + textXOffset, self.__yBase + textYOffset)) diff --git a/kelner/src/managers/DrawableCollection.py b/kelner/src/managers/DrawableCollection.py index cb17915..445880f 100644 --- a/kelner/src/managers/DrawableCollection.py +++ b/kelner/src/managers/DrawableCollection.py @@ -1,4 +1,6 @@ import random +from threading import Lock + from kelner.src.components.Table import Table, Status from kelner.src.components.Waiter import Waiter @@ -6,13 +8,15 @@ from kelner.src.components.Waiter import Waiter # drawable objects manager class DrawableCollection: # const, minimal distance between objects - __MinDistanceX = 0 + __MinDistanceX = 1 __MinDistanceY = 0 def __init__(self): # collection that holds all drawable objects self.__mustRepaint = True self.__drawables = [] + self.__waiterLock = Lock() + self.__tableLock = Lock() # adds drawable objects to the collection def add(self, drawable): @@ -21,6 +25,7 @@ class DrawableCollection: # generates and sets random (x, y) and cheks if it's not occupied by other object def generatePosition(self, drawable): isPositionUnique = False + attempt = 0 while not isPositionUnique: x = random.randint(drawable.getMinX() + 1, drawable.getMaxX() - 1) y = random.randint(drawable.getMinY() + 1, drawable.getMaxY() - 1) @@ -32,6 +37,11 @@ class DrawableCollection: if isPositionUnique: drawable.setX(x) drawable.setY(y) + return True + else: + attempt += 1 + if attempt > 40: + return False # checks if position (x,y) is not occupied by other object def isPositionAvailable(self, x, y): @@ -58,17 +68,6 @@ class DrawableCollection: result += [item] return result - # waiter collects orders from all nearest tables - def collectOrders(self): - 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() - # returns table: 0 - position is available, 1 - position is occupied def getReservedPlaces(self, waiter): cols = waiter.getMaxX() - waiter.getMinX() + 1 @@ -78,8 +77,51 @@ class DrawableCollection: if tables: for table in tables: reservedPlaces[table.getY()][table.getX()] = 1 + waiters = self.getWaiters() + if waiters: + for other in waiters: + if other is not waiter: + reservedPlaces[other.getY()][other.getX()] = 1 return reservedPlaces + def getNearestTables(self, waiter, tableStatus): + 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): + nearestTables.append(table) + return nearestTables + + # waiter collects orders from table + def collectOrder(self, waiter, table): + result = False + self.__tableLock.acquire() + try: + if table.isStatus(Status.Ready) and table.getOrder() != []: + waiter.addOrder(table) + table.delOrder() + result = True + finally: + self.__tableLock.release() + return result + + def moveWaiter(self, someWaiter, x, y): + isPositionAvailable = True + waiters = self.getWaiters() + self.__waiterLock.acquire() + try: + for waiter in waiters: + if waiter is not someWaiter: + if waiter.getX() == x and waiter.getY() == y: + isPositionAvailable = False + break + if isPositionAvailable: + someWaiter.setX(x) + someWaiter.setY(y) + finally: + self.__waiterLock.release() + # the method is called externally and forces repainting def forceRepaint(self): self.__mustRepaint = True diff --git a/kelner/src/managers/ImageCache.py b/kelner/src/managers/ImageCache.py index 4fa85f8..014ae56 100644 --- a/kelner/src/managers/ImageCache.py +++ b/kelner/src/managers/ImageCache.py @@ -1,50 +1,66 @@ -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 +import pygame +from enum import Enum + + +# images enum +class Images(Enum): + Background = 0 + WaiterLeftUp = 1 + WaiterUp = 2 + WaiterRightUp = 3 + WaiterRight = 4 + WaiterRightDown = 5 + WaiterDown = 6 + WaiterLeftDown = 7 + WaiterLeft = 8 + Table = 9 + Menu = 10 + Check = 11 + Plate = 12 + Guest1 = 13 + Guest2 = 14 + Guest3 = 15 + ToolTip = 16 + + +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.WaiterLeftUp: './images/kelner_full_LU.png', + Images.WaiterUp: './images/kelner_full_U.png', + Images.WaiterRightUp: './images/kelner_full_RU.png', + Images.WaiterRight: './images/kelner_full_R.png', + Images.WaiterRightDown: './images/kelner_full_RD.png', + Images.WaiterDown: './images/kelner_full_D.png', + Images.WaiterLeftDown: './images/kelner_full_LD.png', + Images.WaiterLeft: './images/kelner_full_L.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', + Images.ToolTip: './images/tooltip.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/TableManager.py b/kelner/src/managers/TableManager.py index 4ea524a..1eb38cc 100644 --- a/kelner/src/managers/TableManager.py +++ b/kelner/src/managers/TableManager.py @@ -1,29 +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 +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(1) + table.setStatus(Status.Ready) + table.setOrder(self.__menuManager.generateOrder()) + self.__drawableManager.forceRepaint() + + def stop(self): + self.__runThread = False diff --git a/kelner/src/managers/WaiterManager.py b/kelner/src/managers/WaiterManager.py index 2240637..6c6849c 100644 --- a/kelner/src/managers/WaiterManager.py +++ b/kelner/src/managers/WaiterManager.py @@ -1,57 +1,93 @@ -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 +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, waiters): + super().__init__() + self.__drawableManager = drawableManager + self.__waiters = waiters + 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 + + def __changeWaiterDirection(self, waiter, x, y): + targetDirection = x - waiter.getX(), y - waiter.getY() + originDirection = waiter.getDirection() + while originDirection is not None: + originDirection = waiter.getNextDirection(originDirection, targetDirection, True) + if originDirection is not None: + time.sleep(0.3) + waiter.setDirection(originDirection[0], originDirection[1]) + self.__drawableManager.forceRepaint() + + def __moveWaiter(self, waiter, x, y): + time.sleep(0.4) + self.__drawableManager.moveWaiter(waiter, x, y) + self.__drawableManager.forceRepaint() + + def __collectOrder(self, waiter): + doCollectOrder = True + while doCollectOrder: + tables = self.__drawableManager.getNearestTables(waiter, Status.Ready) + turns = sys.maxsize + lessTurnsTable = None + originDirection = waiter.getDirection() + + for table in tables: + targetDirection = table.getX() - waiter.getX(), table.getY() - waiter.getY() + result = Finder.getTurnsCount(originDirection, targetDirection, True) + if result < turns: + turns = result + lessTurnsTable = table + + if lessTurnsTable is not None: + tables.remove(lessTurnsTable) + self.__changeWaiterDirection(waiter, lessTurnsTable.getX(), lessTurnsTable.getY()) + if self.__drawableManager.collectOrder(waiter, lessTurnsTable): + time.sleep(2) + lessTurnsTable.setStatus(Status.Waiting) + self.__drawableManager.forceRepaint() + doCollectOrder = len(tables) > 0 + + # changes the status of a random table from NotReady to Ready + def run(self): + while self.__runThread: + if self.__waiters: + for waiter in self.__waiters: + path = self.__getNearestTargetPath(waiter) + waiter.setPath([] if path is None else path) + + if not waiter.isPathEmpty(): + step = waiter.popStepFromPath() + self.__changeWaiterDirection(waiter, step[0], step[1]) + self.__moveWaiter(waiter, step[0], step[1]) + + if waiter.isPathEmpty(): + self.__collectOrder(waiter) + + def stop(self): + self.__runThread = False