Merge branch 'mikolaj_branch' of s444417/ProjektAI into master

This commit is contained in:
Mikołaj Krzymiński 2020-04-27 10:39:59 +00:00 committed by Gogs
commit 1f167f28cd
25 changed files with 655 additions and 396 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.8" 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.8" 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,60 +1,96 @@
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
from kelner.src.algorithms.BFS.BFS import BFS from kelner.src.algorithms.BFS.BFS import BFS
# 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)
# bfs = BFS(reservedPlaces) finder = Finder(reservedPlaces)
origin = (waiter.getX(), waiter.getY()) # bfs = BFS(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 = bfs.find_path(origin, target) if target is not None:
path = finder.getPath(origin, target, True) # path = bfs.find_path(origin, target)
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