Obracanie kelnera (algorytm i grafika). Kilku kelnerów może chodzić równocześnie. Tooltip z ilością zebranych zamówień.

This commit is contained in:
s444417 2020-04-27 12:22:19 +02:00
parent 760f2607e2
commit dba30bae63
25 changed files with 652 additions and 393 deletions

View File

@ -2,7 +2,7 @@
<module type="PYTHON_MODULE" version="4"> <module type="PYTHON_MODULE" version="4">
<component name="NewModuleRootManager"> <component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" /> <content url="file://$MODULE_DIR$" />
<orderEntry type="jdk" jdkName="Python 3.7 (WaiterMaster)" jdkType="Python SDK" /> <orderEntry type="jdk" jdkName="Python 3.7 (venv)" jdkType="Python SDK" />
<orderEntry type="sourceFolder" forTests="false" /> <orderEntry type="sourceFolder" forTests="false" />
</component> </component>
<component name="TestRunnerService"> <component name="TestRunnerService">

View File

@ -3,5 +3,5 @@
<component name="JavaScriptSettings"> <component name="JavaScriptSettings">
<option name="languageLevel" value="ES6" /> <option name="languageLevel" value="ES6" />
</component> </component>
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.7 (WaiterMaster)" project-jdk-type="Python SDK" /> <component name="ProjectRootManager" version="2" project-jdk-name="Python 3.7 (venv)" project-jdk-type="Python SDK" />
</project> </project>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 184 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 155 KiB

BIN
kelner/images/tooltip.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 365 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

View File

@ -26,24 +26,39 @@ drawableManager = DrawableCollection()
menuManager = MenuManager() menuManager = MenuManager()
# initialize waiter component # 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 # 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 # 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) table = Table(0, GridCountX - 1, 0, GridCountY - 1, CellSize, PaintOffset)
drawableManager.generatePosition(table) if drawableManager.generatePosition(table):
drawableManager.add(table) drawableManager.add(table)
# new thread controlling tables # new thread controlling tables
tableTask = TableManager(drawableManager, menuManager) tableTask = TableManager(drawableManager, menuManager)
tableTask.start() tableTask.start()
# new thread controlling waiter # new thread controlling waiter
waiterTask = WaiterManager(drawableManager) waiter1Task = WaiterManager(drawableManager, [waiter1])
waiterTask.start() waiter1Task.start()
waiter2Task = WaiterManager(drawableManager, [waiter2])
waiter2Task.start()
waiter3Task = WaiterManager(drawableManager, [waiter3])
waiter3Task.start()
waiter4Task = WaiterManager(drawableManager, [waiter4])
waiter4Task.start()
# main loop # main loop
running = True running = True
@ -52,27 +67,51 @@ while running:
for event in pygame.event.get(): for event in pygame.event.get():
if event.type == pygame.QUIT: if event.type == pygame.QUIT:
tableTask.stop() tableTask.stop()
waiterTask.stop() waiter1Task.stop()
waiter2Task.stop()
waiter3Task.stop()
waiter4Task.stop()
running = False running = False
# handles keyboard events # handles keyboard events
if event.type == pygame.KEYDOWN: if event.type == pygame.KEYDOWN:
if event.key == pygame.K_LEFT: 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()): if drawableManager.isPositionAvailable(waiter1.getX() - 1, waiter1.getY()):
waiter.moveLeft() waiter1.moveLeft()
if event.key == pygame.K_RIGHT: if event.key == pygame.K_RIGHT:
# checks if new waiter's position to the right is not occupied by other object # checks if new waiter's position to the right is not occupied by other object
if drawableManager.isPositionAvailable(waiter.getX() + 1, waiter.getY()): if drawableManager.isPositionAvailable(waiter1.getX() + 1, waiter1.getY()):
waiter.moveRight() waiter1.moveRight()
if event.key == pygame.K_UP: if event.key == pygame.K_UP:
# checks if new waiter's position up is not occupied by other object # checks if new waiter's position up is not occupied by other object
if drawableManager.isPositionAvailable(waiter.getX(), waiter.getY() - 1): if drawableManager.isPositionAvailable(waiter1.getX(), waiter1.getY() - 1):
waiter.moveUp() waiter1.moveUp()
if event.key == pygame.K_DOWN: if event.key == pygame.K_DOWN:
# checks if new waiter's position down is not occupied by other object # checks if new waiter's position down is not occupied by other object
if drawableManager.isPositionAvailable(waiter.getX(), waiter.getY() + 1): if drawableManager.isPositionAvailable(waiter1.getX(), waiter1.getY() + 1):
waiter.moveDown() 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() drawableManager.forceRepaint()
# repaints all objects to the screen # repaints all objects to the screen

View File

@ -1,125 +1,140 @@
from queue import PriorityQueue from queue import PriorityQueue
from kelner.src.algorithms.AStar.Node import Node from kelner.src.algorithms.AStar.Node import Node
class Finder: class Finder:
def __init__(self, table): def __init__(self, table):
self._table = table self._table = table
self._xMin = 0 self._xMin = 0
self._yMin = 0 self._yMin = 0
self._xMax = len(table[0]) - 1 self._xMax = len(table[0]) - 1
self._yMax = len(table) - 1 self._yMax = len(table) - 1
self.__isDiagonal = False
def _set(self, x, y, v):
def _set(self, x, y, v): self._table[y][x] = v
self._table[y][x] = v
def _get(self, x, y):
def _get(self, x, y): return self._table[y][x]
return self._table[y][x]
# returns right position relative to the node
# returns right position relative to the node def __incXnopY(self, node, direction, isDiagonal, neighbours):
def __incXnopY(self, node, neighbours): if node.x < self._xMax and self._get(node.x + 1, node.y) == 0:
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 + 1)) neighbours.append(Node(node, node.x + 1, node.y, node.distance + turnsCount + 1))
# returns left position relative to the node # returns left position relative to the node
def __decXnopY(self, node, neighbours): def __decXnopY(self, node, direction, isDiagonal, neighbours):
if self._xMin < node.x and self._get(node.x - 1, node.y) == 0: 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)) 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, neighbours): # returns top position relative to the node
if node.y < self._yMax and self._get(node.x, node.y + 1) == 0: def __nopXincY(self, node, direction, isDiagonal, neighbours):
neighbours.append(Node(node, node.x, node.y + 1, node.distance + 1)) if node.y < self._yMax and self._get(node.x, node.y + 1) == 0:
turnsCount = self.getTurnsCount(direction, (0, 1), isDiagonal)
# returns bottom position relative to the node neighbours.append(Node(node, node.x, node.y + 1, node.distance + turnsCount + 1))
def __nopXdecY(self, node, neighbours):
if self._yMin < node.y and self._get(node.x, node.y - 1) == 0: # returns bottom position relative to the node
neighbours.append(Node(node, node.x, node.y - 1, node.distance + 1)) def __nopXdecY(self, node, direction, isDiagonal, neighbours):
if self._yMin < node.y and self._get(node.x, node.y - 1) == 0:
# returns left top position relative to the node turnsCount = self.getTurnsCount(direction, (0, -1), isDiagonal)
def __decXdecY(self, node, neighbours): neighbours.append(Node(node, node.x, node.y - 1, node.distance + turnsCount + 1))
if (self._xMin < node.x and self._yMin < node.y and
self._get(node.x - 1, node.y - 1) == 0 and # returns left top position relative to the node
self._get(node.x - 1, node.y) == 0 and def __decXdecY(self, node, direction, isDiagonal, neighbours):
self._get(node.x, node.y - 1) == 0): if (self._xMin < node.x and self._yMin < node.y and
neighbours.append(Node(node, node.x - 1, node.y - 1, node.distance + 2)) self._get(node.x - 1, node.y - 1) == 0 and
self._get(node.x - 1, node.y) == 0 and
# returns left bottom position relative to the node self._get(node.x, node.y - 1) == 0):
def __decXincY(self, node, neighbours): turnsCount = self.getTurnsCount(direction, (-1, -1), isDiagonal)
if (self._xMin < node.x and node.y < self._yMax and neighbours.append(Node(node, node.x - 1, node.y - 1, node.distance + turnsCount + 2))
self._get(node.x - 1, node.y + 1) == 0 and
self._get(node.x - 1, node.y) == 0 and # returns left bottom position relative to the node
self._get(node.x, node.y + 1) == 0): def __decXincY(self, node, direction, isDiagonal, neighbours):
neighbours.append(Node(node, node.x - 1, node.y + 1, node.distance + 2)) if (self._xMin < node.x and node.y < self._yMax and
self._get(node.x - 1, node.y + 1) == 0 and
# returns right bottom position relative to the node self._get(node.x - 1, node.y) == 0 and
def __incXincY(self, node, neighbours): self._get(node.x, node.y + 1) == 0):
if (node.x < self._xMax and node.y < self._yMax and turnsCount = self.getTurnsCount(direction, (-1, 1), isDiagonal)
self._get(node.x + 1, node.y + 1) == 0 and neighbours.append(Node(node, node.x - 1, node.y + 1, node.distance + turnsCount + 2))
self._get(node.x + 1, node.y) == 0 and
self._get(node.x, node.y + 1) == 0): # returns right bottom position relative to the node
neighbours.append(Node(node, node.x + 1, node.y + 1, node.distance + 2)) def __incXincY(self, node, direction, isDiagonal, neighbours):
if (node.x < self._xMax and node.y < self._yMax and
# returns right top position relative to the node self._get(node.x + 1, node.y + 1) == 0 and
def __incXdecY(self, node, neighbours): self._get(node.x + 1, node.y) == 0 and
if (node.x < self._xMax and self._yMin < node.y and self._get(node.x, node.y + 1) == 0):
self._get(node.x + 1, node.y - 1) == 0 and turnsCount = self.getTurnsCount(direction, (1, 1), isDiagonal)
self._get(node.x + 1, node.y) == 0 and neighbours.append(Node(node, node.x + 1, node.y + 1, node.distance + turnsCount + 2))
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, direction, isDiagonal, neighbours):
# returns all plausible positions relative to the node if (node.x < self._xMax and self._yMin < node.y and
def __getNeighbours(self, node): self._get(node.x + 1, node.y - 1) == 0 and
neighbours = [] self._get(node.x + 1, node.y) == 0 and
self.__nopXincY(node, neighbours) self._get(node.x, node.y - 1) == 0):
self.__incXnopY(node, neighbours) turnsCount = self.getTurnsCount(direction, (1, -1), isDiagonal)
self.__decXnopY(node, neighbours) neighbours.append(Node(node, node.x + 1, node.y - 1, node.distance + turnsCount + 2))
self.__nopXdecY(node, neighbours)
if self.__isDiagonal: # returns all plausible positions relative to the node
self.__decXdecY(node, neighbours) def __getNeighbours(self, node, isDiagonal):
self.__decXincY(node, neighbours) direction = node.getDirection()
self.__incXincY(node, neighbours) neighbours = []
self.__incXdecY(node, neighbours) self.__nopXincY(node, direction, isDiagonal, neighbours)
return neighbours self.__incXnopY(node, direction, isDiagonal, neighbours)
self.__decXnopY(node, direction, isDiagonal, neighbours)
# main algorithm - simplification of well known A* self.__nopXdecY(node, direction, isDiagonal, neighbours)
def __getPath(self, origin, target): if isDiagonal:
Q = PriorityQueue() self.__decXdecY(node, direction, isDiagonal, neighbours)
V = set() self.__decXincY(node, direction, isDiagonal, neighbours)
Q.put(origin) self.__incXincY(node, direction, isDiagonal, neighbours)
while not Q.empty(): self.__incXdecY(node, direction, isDiagonal, neighbours)
head = Q.get() return neighbours
if head == target:
return head # main algorithm - simplification of well known A*
V.add(head) def __getPath(self, origin, target, isDiagonal):
for node in self.__getNeighbours(head): Q = PriorityQueue()
if node not in V: V = set()
node.estimated = node.distance + node.getDistanceTo(target) Q.put(origin)
Q.put(node) while not Q.empty():
V.add(node) head = Q.get()
return None if head == target:
return head
# returns neighbours for locationXY-tuple as list of tuple(x,y) V.add(head)
def getNeighbours(self, locationXY, isDiagonal): for node in self.__getNeighbours(head, isDiagonal):
neighboursXY = [] if node not in V:
self.__isDiagonal = isDiagonal node.estimated = node.distance + node.getDistanceTo(target)
location = Node(None, locationXY[0], locationXY[1], 0) Q.put(node)
neighbours = self.__getNeighbours(location) V.add(node)
for neighbour in neighbours: return None
neighboursXY.append((neighbour.x, neighbour.y))
return neighboursXY # returns the number of turns to change direction from old to new
@staticmethod
# returns the shortest path as list of tuple(x,y) from originXY-tuple to targetXY-tuple def getTurnsCount(oldDirection, newDirection, isDiagonal):
def getPath(self, originXY, targetXY, isDiagonal): if oldDirection == (0, 0) or oldDirection == newDirection:
self.__isDiagonal = isDiagonal return 0
origin = Node(None, originXY[0], originXY[1], 0) if 0 == oldDirection[0] + newDirection[0] and 0 == oldDirection[1] + newDirection[1]:
target = Node(None, targetXY[0], targetXY[1], 0) return 4 if isDiagonal else 2
result = self.__getPath(origin, target) return abs(newDirection[0] - oldDirection[0]) + abs(newDirection[1] - oldDirection[1]) if isDiagonal else 1
path = []
while result is not None: # returns neighbours for locationXY-tuple as list of tuple(x,y)
if result.parent is not None: def getNeighbours(self, locationXY, isDiagonal):
path.insert(0, (result.x, result.y)) neighboursXY = []
result = result.parent location = Node(None, locationXY[0], locationXY[1], 0)
return path 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

View File

@ -1,60 +1,60 @@
from random import randint import random
from kelner.src.algorithms.AStar.Finder import Finder from kelner.src.algorithms.AStar.Finder import Finder
class FinderTest(Finder): class FinderTest(Finder):
def __init__(self, table): def __init__(self, table):
super().__init__(table) super().__init__(table)
def __setValues(self, xys, v): def __setValues(self, xys, v):
if xys is not None: if xys is not None:
for xy in xys: for xy in xys:
self._set(xy[0], xy[1], v) self._set(xy[0], xy[1], v)
def print(self, xys): def print(self, xys):
self.__setValues(xys, 2) self.__setValues(xys, 2)
for row in self._table: for row in self._table:
for col in row: for col in row:
v = ' ' if col == 0 else '#' if col == 1 else 'O' v = ' ' if col == 0 else '#' if col == 1 else 'O'
print('|', v, sep='', end='') print('|', v, sep='', end='')
print('|') print('|')
self.__setValues(xys, 0) self.__setValues(xys, 0)
def getRandomTuple(self): def getRandomTuple(self):
while True: while True:
x = randint(self._xMin, self._xMax) x = random.randint(self._xMin, self._xMax)
y = randint(self._yMin, self._yMax) y = random.randint(self._yMin, self._yMax)
if self._get(x, y) == 0: if self._get(x, y) == 0:
break break
return x, y return x, y
def getRandomBorderTuple(self): def getRandomBorderTuple(self):
xSet = [self._xMin, self._xMax] xSet = [self._xMin, self._xMax]
ySet = [self._yMin, self._yMax] ySet = [self._yMin, self._yMax]
while True: while True:
x = randint(self._xMin, self._xMax) x = random.randint(self._xMin, self._xMax)
y = randint(self._yMin, self._yMax) y = random.randint(self._yMin, self._yMax)
if (x in xSet or y in ySet) and self._get(x, y) == 0: if (x in xSet or y in ySet) and self._get(x, y) == 0:
break break
return x, y return x, y
def fillRandom(self): def fillRandom(self):
for _ in range(120): for _ in range(120):
while True: while True:
x = randint(self._xMin, self._xMax) x = random.randint(self._xMin, self._xMax)
y = randint(self._yMin, self._yMax) y = random.randint(self._yMin, self._yMax)
if self._get(x, y) == 0: if self._get(x, y) == 0:
break break
self._set(x, y, 1) self._set(x, y, 1)
cols = 20 cols = 20
rows = 20 rows = 20
table = [[0] * cols for i in range(rows)] table = [[0] * cols for i in range(rows)]
finder = FinderTest(table) finder = FinderTest(table)
finder.fillRandom() finder.fillRandom()
originXY = finder.getRandomBorderTuple() originXY = finder.getRandomBorderTuple()
targetXY = finder.getRandomBorderTuple() targetXY = finder.getRandomBorderTuple()
result = finder.getPath(originXY, targetXY, True) result = finder.getPath(originXY, targetXY, True)
finder.print(result) finder.print(result)

View File

@ -1,34 +1,40 @@
from math import sqrt from math import sqrt
class Node: class Node:
def __init__(self, parent, x, y, distance): def __init__(self, parent, x, y, distance):
self.parent = parent self.parent = parent
self.x = x self.x = x
self.y = y self.y = y
self.distance = distance self.distance = distance
self.estimated = distance self.estimated = distance
# returns distance from the object to other node # returns distance from the object to other node
def getDistanceTo(self, other): def getDistanceTo(self, other):
dx = self.x - other.x dx = self.x - other.x
dy = self.y - other.y dy = self.y - other.y
return sqrt(dx * dx + dy * dy) return sqrt(dx * dx + dy * dy)
# abs(dx) + abs(dy) # abs(dx) + abs(dy)
# used by str() method to represent the object def getDirection(self):
def __repr__(self): if self.parent is None:
return "%s:%s" % (self.x, self.y) return 0, 0
else:
# generates hash key for Set return self.x - self.parent.x, self.y - self.parent.y
def __hash__(self):
return hash(str(self)) # used by str() method to represent the object
def __repr__(self):
# operator (==) for Set (determines if the object equals other node) return "%s:%s" % (self.x, self.y)
def __eq__(self, other):
return (self.x == other.x) and (self.y == other.y) # generates hash key for Set
def __hash__(self):
# operator (>) for PriorityQueue comparison (determines the objects order) return hash(str(self))
def __gt__(self, other):
return self.estimated > other.estimated # 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

View File

@ -10,10 +10,10 @@ class Drawable:
self.__maxX = maxX self.__maxX = maxX
self.__minY = minY self.__minY = minY
self.__maxY = maxY self.__maxY = maxY
self.setX(x) self.__x = x
self.setY(y) self.__y = y
self.__cellSize = cellSize # cell size in pixels self.__cellSize = cellSize # cell size in pixels
self.__offset = offset # paint offset in pixels self.__offset = offset # paint offset in pixels
def setX(self, x): def setX(self, x):
if x < self.__minX or self.__maxX < x: if x < self.__minX or self.__maxX < x:

View File

@ -1,18 +1,35 @@
import random
import pygame
from kelner.src.components.Drawable import Drawable from kelner.src.components.Drawable import Drawable
from kelner.src.managers.ImageCache import ImageCache, Images 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): class Waiter(Drawable):
def __init__(self, x, y, minX, maxX, minY, maxY, ratio, offset): def __init__(self, x, y, minX, maxX, minY, maxY, ratio, offset):
# call base class constructor # call base class constructor
super().__init__(x, y, minX, maxX, minY, maxY, ratio, offset) super().__init__(x, y, minX, maxX, minY, maxY, ratio, offset)
self.__dx = Direction.Down[0]
self.__dy = Direction.Down[1]
self.__acceptedOrders = [] self.__acceptedOrders = []
self.__currentPath = [] self.__currentPath = []
def moveUp(self): def moveUp(self):
if self.getY() > self.getMinY(): if self.getY() > self.getMinY():
self.setY(self.getY() - 1) self.setY(self.getY() - 1)
self.setX(self.getX())
return True return True
else: else:
return False return False
@ -20,6 +37,7 @@ class Waiter(Drawable):
def moveDown(self): def moveDown(self):
if self.getY() < self.getMaxY(): if self.getY() < self.getMaxY():
self.setY(self.getY() + 1) self.setY(self.getY() + 1)
self.setX(self.getX())
return True return True
else: else:
return False return False
@ -27,6 +45,7 @@ class Waiter(Drawable):
def moveLeft(self): def moveLeft(self):
if self.getX() > self.getMinX(): if self.getX() > self.getMinX():
self.setX(self.getX() - 1) self.setX(self.getX() - 1)
self.setY(self.getY())
return True return True
else: else:
return False return False
@ -34,10 +53,56 @@ class Waiter(Drawable):
def moveRight(self): def moveRight(self):
if self.getX() < self.getMaxX(): if self.getX() < self.getMaxX():
self.setX(self.getX() + 1) self.setX(self.getX() + 1)
self.setY(self.getY())
return True return True
else: else:
return False 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 # accepts orders from the table and stores them in queue
def addOrder(self, table): def addOrder(self, table):
self.__acceptedOrders += [(table, table.getOrder())] self.__acceptedOrders += [(table, table.getOrder())]
@ -52,7 +117,47 @@ class Waiter(Drawable):
return self.__currentPath.pop(0) return self.__currentPath.pop(0)
def draw(self, screen): def draw(self, screen):
imageWaiter = ImageCache.getInstance().getImage(Images.Waiter, self.getCellSize(), self.getCellSize()) direction = self.getDirection()
xBase = self.getX() * self.getCellSize() + self.getOffset() imageKind = None
yBase = self.getY() * self.getCellSize() + self.getOffset() if direction == Direction.LeftUp:
screen.blit(imageWaiter, (xBase, yBase)) 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))

View File

@ -1,4 +1,6 @@
import random import random
from threading import Lock
from kelner.src.components.Table import Table, Status from kelner.src.components.Table import Table, Status
from kelner.src.components.Waiter import Waiter from kelner.src.components.Waiter import Waiter
@ -6,13 +8,15 @@ from kelner.src.components.Waiter import Waiter
# drawable objects manager # drawable objects manager
class DrawableCollection: class DrawableCollection:
# const, minimal distance between objects # const, minimal distance between objects
__MinDistanceX = 0 __MinDistanceX = 1
__MinDistanceY = 0 __MinDistanceY = 0
def __init__(self): def __init__(self):
# collection that holds all drawable objects # collection that holds all drawable objects
self.__mustRepaint = True self.__mustRepaint = True
self.__drawables = [] self.__drawables = []
self.__waiterLock = Lock()
self.__tableLock = Lock()
# adds drawable objects to the collection # adds drawable objects to the collection
def add(self, drawable): 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 # generates and sets random (x, y) and cheks if it's not occupied by other object
def generatePosition(self, drawable): def generatePosition(self, drawable):
isPositionUnique = False isPositionUnique = False
attempt = 0
while not isPositionUnique: while not isPositionUnique:
x = random.randint(drawable.getMinX() + 1, drawable.getMaxX() - 1) x = random.randint(drawable.getMinX() + 1, drawable.getMaxX() - 1)
y = random.randint(drawable.getMinY() + 1, drawable.getMaxY() - 1) y = random.randint(drawable.getMinY() + 1, drawable.getMaxY() - 1)
@ -32,6 +37,11 @@ class DrawableCollection:
if isPositionUnique: if isPositionUnique:
drawable.setX(x) drawable.setX(x)
drawable.setY(y) drawable.setY(y)
return True
else:
attempt += 1
if attempt > 40:
return False
# 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): def isPositionAvailable(self, x, y):
@ -58,17 +68,6 @@ class DrawableCollection:
result += [item] result += [item]
return result 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 # returns table: 0 - position is available, 1 - position is occupied
def getReservedPlaces(self, waiter): def getReservedPlaces(self, waiter):
cols = waiter.getMaxX() - waiter.getMinX() + 1 cols = waiter.getMaxX() - waiter.getMinX() + 1
@ -78,8 +77,51 @@ class DrawableCollection:
if tables: if tables:
for table in tables: for table in tables:
reservedPlaces[table.getY()][table.getX()] = 1 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 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 # the method is called externally and forces repainting
def forceRepaint(self): def forceRepaint(self):
self.__mustRepaint = True self.__mustRepaint = True

View File

@ -1,50 +1,66 @@
import pygame import pygame
from enum import Enum from enum import Enum
# images enum # images enum
class Images(Enum): class Images(Enum):
Background = 0 Background = 0
Waiter = 1 WaiterLeftUp = 1
Table = 2 WaiterUp = 2
Menu = 3 WaiterRightUp = 3
Check = 4 WaiterRight = 4
Plate = 5 WaiterRightDown = 5
Guest1 = 6 WaiterDown = 6
Guest2 = 7 WaiterLeftDown = 7
Guest3 = 8 WaiterLeft = 8
Table = 9
Menu = 10
class ImageCache: Check = 11
__instance = None Plate = 12
Guest1 = 13
@staticmethod Guest2 = 14
def getInstance(): Guest3 = 15
if ImageCache.__instance is None: ToolTip = 16
ImageCache()
return ImageCache.__instance
class ImageCache:
def __init__(self): __instance = None
""" Virtually private constructor. """
if ImageCache.__instance is not None: @staticmethod
raise Exception("This class is a singleton!") def getInstance():
else: if ImageCache.__instance is None:
ImageCache.__instance = self ImageCache()
self.__images = {} return ImageCache.__instance
self.__paths = {Images.Background: './images/Backgroud.png',
Images.Waiter: './images/kelner.png', def __init__(self):
Images.Table: './images/stol.png', """ Virtually private constructor. """
Images.Menu: './images/ksiazka.png', if ImageCache.__instance is not None:
Images.Check: './images/check.png', raise Exception("This class is a singleton!")
Images.Plate: './images/plate.png', else:
Images.Guest1: './images/wiking_blond.png', ImageCache.__instance = self
Images.Guest2: './images/wiking_rudy.png', self.__images = {}
Images.Guest3: './images/wiking_rudy2.png'} self.__paths = {Images.Background: './images/Backgroud.png',
Images.WaiterLeftUp: './images/kelner_full_LU.png',
def getImage(self, imageKind, width, height): Images.WaiterUp: './images/kelner_full_U.png',
key = str(imageKind.value) + ':' + str(width) + ':' + str(height) Images.WaiterRightUp: './images/kelner_full_RU.png',
image = self.__images.get(key, None) Images.WaiterRight: './images/kelner_full_R.png',
if image is None: Images.WaiterRightDown: './images/kelner_full_RD.png',
image = pygame.transform.scale((pygame.image.load(self.__paths[imageKind])), (width, height)) Images.WaiterDown: './images/kelner_full_D.png',
self.__images[key] = image Images.WaiterLeftDown: './images/kelner_full_LD.png',
return image 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

View File

@ -1,29 +1,29 @@
import threading import threading
import time import time
import random import random
from kelner.src.components.Table import Status from kelner.src.components.Table import Status
# creates new thread # creates new thread
class TableManager (threading.Thread): class TableManager (threading.Thread):
def __init__(self, drawableManager, menuManager): def __init__(self, drawableManager, menuManager):
super().__init__() super().__init__()
self.__drawableManager = drawableManager self.__drawableManager = drawableManager
self.__menuManager = menuManager self.__menuManager = menuManager
self.__runThread = True self.__runThread = True
# changes the status of a random table from NotReady to Ready # changes the status of a random table from NotReady to Ready
def run(self): def run(self):
while self.__runThread: while self.__runThread:
tables = self.__drawableManager.getTables(Status.NotReady) tables = self.__drawableManager.getTables(Status.NotReady)
if tables: if tables:
tableIndex = random.randint(0, len(tables) - 1) tableIndex = random.randint(0, len(tables) - 1)
table = tables[tableIndex] table = tables[tableIndex]
time.sleep(3) time.sleep(1)
table.setStatus(Status.Ready) table.setStatus(Status.Ready)
table.setOrder(self.__menuManager.generateOrder()) table.setOrder(self.__menuManager.generateOrder())
self.__drawableManager.forceRepaint() self.__drawableManager.forceRepaint()
def stop(self): def stop(self):
self.__runThread = False self.__runThread = False

View File

@ -1,57 +1,93 @@
import threading import threading
import time import time
import sys import sys
from kelner.src.components.Table import Status from kelner.src.components.Table import Status
from kelner.src.algorithms.AStar.Finder import Finder from kelner.src.algorithms.AStar.Finder import Finder
# creates new thread # creates new thread
class WaiterManager (threading.Thread): class WaiterManager (threading.Thread):
def __init__(self, drawableManager): def __init__(self, drawableManager, waiters):
super().__init__() super().__init__()
self.__drawableManager = drawableManager self.__drawableManager = drawableManager
self.__runThread = True self.__waiters = waiters
self.__runThread = True
def __getNearestTargetPath(self, waiter):
distance = sys.maxsize def __getNearestTargetPath(self, waiter):
nearestTargetPath = None distance = sys.maxsize
tables = self.__drawableManager.getTables(Status.Ready) nearestTargetPath = None
if tables: tables = self.__drawableManager.getTables(Status.Ready)
reservedPlaces = self.__drawableManager.getReservedPlaces(waiter) if tables:
finder = Finder(reservedPlaces) reservedPlaces = self.__drawableManager.getReservedPlaces(waiter)
origin = (waiter.getX(), waiter.getY()) finder = Finder(reservedPlaces)
for table in tables: origin = (waiter.getX(), waiter.getY())
targets = finder.getNeighbours((table.getX(), table.getY()), False) for table in tables:
for target in targets: targets = finder.getNeighbours((table.getX(), table.getY()), False)
if target is not None: for target in targets:
path = finder.getPath(origin, target, True) if target is not None:
if path: path = finder.getPath(origin, target, True)
result = len(path) if path:
if result < distance: result = len(path)
distance = result if result < distance:
nearestTargetPath = path distance = result
return nearestTargetPath nearestTargetPath = path
return nearestTargetPath
# changes the status of a random table from NotReady to Ready
def run(self): def __changeWaiterDirection(self, waiter, x, y):
while self.__runThread: targetDirection = x - waiter.getX(), y - waiter.getY()
waiters = self.__drawableManager.getWaiters() originDirection = waiter.getDirection()
if waiters: while originDirection is not None:
for waiter in waiters: originDirection = waiter.getNextDirection(originDirection, targetDirection, True)
path = self.__getNearestTargetPath(waiter) if originDirection is not None:
if path is not None: time.sleep(0.3)
waiter.setPath(path) waiter.setDirection(originDirection[0], originDirection[1])
if not waiter.isPathEmpty(): self.__drawableManager.forceRepaint()
step = waiter.popStepFromPath()
time.sleep(0.4) def __moveWaiter(self, waiter, x, y):
waiter.setX(step[0]) time.sleep(0.4)
waiter.setY(step[1]) self.__drawableManager.moveWaiter(waiter, x, y)
self.__drawableManager.forceRepaint() self.__drawableManager.forceRepaint()
if waiter.isPathEmpty():
time.sleep(2) def __collectOrder(self, waiter):
self.__drawableManager.collectOrders() doCollectOrder = True
self.__drawableManager.forceRepaint() while doCollectOrder:
tables = self.__drawableManager.getNearestTables(waiter, Status.Ready)
def stop(self): turns = sys.maxsize
self.__runThread = False 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