This commit is contained in:
magdabiadala 2020-05-08 23:28:53 +02:00
commit 45136dcea0
4 changed files with 181 additions and 36 deletions

103
agent.py
View File

@ -1,13 +1,17 @@
from warehouse import Coordinates, Tile, Pack from warehouse import Coordinates, Tile, Pack
from queue import PriorityQueue from queue import PriorityQueue
from math import sqrt from math import sqrt
from attributes import TURN_LEFT_DIRECTIONS, TURN_RIGHT_DIRECTIONS from attributes import TURN_LEFT_DIRECTIONS, TURN_RIGHT_DIRECTIONS, PackStatus
import pygame
import sys
class Node: class Node:
def __init__(self, coord_x, coord_y): def __init__(self, coord_x, coord_y, package=None, is_rack=False):
self.x = coord_x self.x = coord_x
self.y = coord_y self.y = coord_y
self.parent = None self.parent = None
self.package = package
self.is_rack = is_rack
self.g_cost = 0 self.g_cost = 0
self.h_cost = 0 self.h_cost = 0
def __eq__(self, other): def __eq__(self, other):
@ -38,12 +42,20 @@ class Agent:
def find_path(self): def find_path(self):
self.closed = [] self.closed = []
self.path = []
self.open = PriorityQueue() self.open = PriorityQueue()
if self.is_loaded:
rack = self.find_nearest_rack_for(self.transported_package)
self.dest = Node(rack.x_position, rack.y_position, is_rack=True)
else:
package = self.find_nearest_package()
self.dest = Node(package.lays_on_field.x_position, package.lays_on_field.y_position, package=package)
start_node = Node(self.x, self.y) start_node = Node(self.x, self.y)
self.open.put((0, start_node)) self.open.put((0, start_node))
while self.open: while self.open:
_, current_node = self.open.get() _, current_node = self.open.get()
print(current_node.x, current_node.y)
self.closed.append(current_node) self.closed.append(current_node)
if current_node.x == self.dest.x and current_node.y == self.dest.y: if current_node.x == self.dest.x and current_node.y == self.dest.y:
while current_node.x != start_node.x or current_node.y != start_node.y: while current_node.x != start_node.x or current_node.y != start_node.y:
@ -77,7 +89,16 @@ class Agent:
def heuristic(self, start: Node, goal: Node): def heuristic(self, start: Node, goal: Node):
diff_x = pow(goal.x - start.x, 2) diff_x = pow(goal.x - start.x, 2)
diff_y = pow(goal.y - start.y, 2) diff_y = pow(goal.y - start.y, 2)
return round(sqrt(diff_x + diff_y), 3) additional_cost = 0
# if diff_x < diff_y:
# column = self.warehouse.tiles[start.x]
# tiles = column[goal.y:start.y] if goal.y < start.y else column[start.y:goal.y]
# additional_cost += len([t for t in tiles if not t.category.passable])
# elif diff_x > diff_y:
# row = [col[start.x] for col in self.warehouse.tiles]
# tiles = row[goal.x:start.x] if goal.x < start.x else row[start.x:goal.x]
# additional_cost += len([t for t in tiles if not t.category.passable])
return round(sqrt(diff_x + diff_y), 3) + float(10*additional_cost)
def check_if_open(self, node: Node): def check_if_open(self, node: Node):
return (node.x, node.y) in [(n.x, n.y) for (_,n) in self.open.queue] return (node.x, node.y) in [(n.x, n.y) for (_,n) in self.open.queue]
@ -98,13 +119,13 @@ class Agent:
return neighbours return neighbours
def move(self): def move(self):
dest_coords = (self.dest.x, self.dest.y)
if not self.path: if not self.path:
if not self.find_path(): if not self.find_path():
return return
else: else:
next = self.path.pop() next = self.path.pop()
star_dir = self.direction star_dir = self.direction
print(next.x, next.y)
if self.x > next.x and not self.direction == 'left': if self.x > next.x and not self.direction == 'left':
if self.direction == 'down': if self.direction == 'down':
self.turn_right() self.turn_right()
@ -126,6 +147,14 @@ class Agent:
else: else:
self.turn_left() self.turn_left()
if (next.x, next.y) == dest_coords:
if self.dest.package:
self.pick_up_package(self.dest.package)
return
elif self.dest.is_rack:
self.unload_package(self.dest)
return
if star_dir == self.direction: if star_dir == self.direction:
self.x = next.x self.x = next.x
self.y = next.y self.y = next.y
@ -135,15 +164,75 @@ class Agent:
def check_if_can_move(self, next_coords: Coordinates): def check_if_can_move(self, next_coords: Coordinates):
tile_on_map = 0 <= next_coords.x < self.warehouse.width and 0 <= next_coords.y < self.warehouse.height tile_on_map = 0 <= next_coords.x < self.warehouse.width and 0 <= next_coords.y < self.warehouse.height
tile_passable = True
if not tile_on_map: if not tile_on_map:
return False return False
next_tile = self.warehouse.tiles[next_coords.x][next_coords.y] next_tile = self.warehouse.tiles[next_coords.x][next_coords.y]
if not self.is_loaded or (next_coords.x, next_coords.y) != (self.dest.x, self.dest.y):
tile_passable = isinstance(next_tile, Tile) and next_tile.category.passable tile_passable = isinstance(next_tile, Tile) and next_tile.category.passable
return tile_passable return tile_passable
def find_nearest_package(self):
packages_costs = []
start_node = Node(self.x, self.y)
if not self.warehouse.packages:
return None
for package in self.warehouse.packages:
if package.status == PackStatus.STORED:
continue
new_node = Node(package.lays_on_field.x_position, package.lays_on_field.y_position)
cost = self.heuristic(start_node, new_node)
if cost > 0:
packages_costs.append((package, cost))
if not packages_costs:
return
# pygame.quit()
# sys.exit()
package = min(packages_costs, key=lambda l: l[1])[0]
return package
def find_nearest_rack_for(self, package, expand_box=0):
weight = package.size
storage = "Rack"
if package.category == "freezed":
storage = "Fridge"
start_node = Node(self.x, self.y)
quarter_x = int(self.warehouse.width/4) + expand_box
quarter_y = int(self.warehouse.height/4) + expand_box
start_quarter_x = self.x - quarter_x if self.x - quarter_x > 0 else 0
end_quarter_x = self.x + quarter_x if self.x + quarter_x < self.warehouse.width else self.warehouse.width - 1
start_quarter_y = self.y - quarter_y if self.y - quarter_y > 0 else 0
end_quarter_y = self.y + quarter_y if self.y + quarter_y < self.warehouse.height else self.warehouse.height - 1
quarter = [row[start_quarter_y:end_quarter_y] for row in self.warehouse.tiles[start_quarter_x:end_quarter_x]]
quarter_racks = [[t for t in row if t.category.name == storage and t.capacity >= weight] for row in quarter]
quarter_racks = [t for row in quarter_racks for t in row]
racks_costs = []
for rack in quarter_racks:
new_node = Node(rack.x_position, rack.y_position)
cost = self.heuristic(start_node, new_node)
if cost > 0:
racks_costs.append((rack, cost))
rack = self.find_nearest_rack_for(package, expand_box + 1) if not racks_costs else min(racks_costs, key=lambda l: l[1])[0]
return rack
def pick_up_package(self, pack): def pick_up_package(self, pack):
tile = pack.lays_on_field self.warehouse.packages.remove(pack)
self.assigned_warehouse.tiles[tile.x_position][tile.y_position] = tile
self.is_loaded = True self.is_loaded = True
if pack.lays_on_field.category.name in ['Rack', 'Fridge']:
pack.lays_on_field.capacity += pack.size
self.dest.package = None
pack.lays_on_field = None pack.lays_on_field = None
self.transported_package = pack self.transported_package = pack
def unload_package(self, rack):
pack = self.transported_package
tile = self.warehouse.tiles[rack.x][rack.y]
self.transported_package = None
self.is_loaded = False
pack.lays_on_field = tile
pack.lays_on_field.capacity -= pack.size
pack.status = PackStatus.STORED
self.warehouse.packages.append(pack)

View File

@ -1,11 +1,5 @@
from enum import Enum from enum import Enum
class PackSize(Enum):
ALL = 0
SMALL = 1
MEDIUM = 2
LARGE = 3
HUGE = 4
class PackStatus(Enum): class PackStatus(Enum):
LOOSE = 0 LOOSE = 0
@ -25,7 +19,9 @@ COLORS = {
'yellow': (235, 235, 0), 'yellow': (235, 235, 0),
'lightgreen': (70, 238, 70), 'lightgreen': (70, 238, 70),
'red': (255, 0, 0), 'red': (255, 0, 0),
'lightblue': (135, 206, 250), 'lightblue': (120, 180, 230),
'iceblue': (186, 242, 239),
'blue': (0, 0, 255),
'orange': (255, 165, 0) 'orange': (255, 165, 0)
} }

24
main.py
View File

@ -3,14 +3,16 @@ import warehouse
import agent import agent
import random import random
import sys import sys
from attributes import PackSize, PackStatus, COLORS, DIRECTION_ANGLES from attributes import PackStatus, COLORS, DIRECTION_ANGLES
WINDOW_SIZE = (640, 640) WINDOW_SIZE = (640, 640)
COLOR_OF_FIELD = { COLOR_OF_FIELD = {
'Floor': 'gray', 'Floor': 'gray',
'Rack': 'white', 'Rack': 'white',
'Pack': 'yellow', 'Pack': 'yellow',
'path': 'orange' 'path': 'orange',
'FridgeFloor': 'lightblue',
'Fridge': 'iceblue'
} }
TILE_WIDTH = 32 TILE_WIDTH = 32
TILE_HEIGHT = 32 TILE_HEIGHT = 32
@ -18,9 +20,12 @@ CIRCLE_CENTER_X, CIRCLE_CENTER_Y = int(TILE_WIDTH/2), int(TILE_HEIGHT/2)
class MainGameFrame: class MainGameFrame:
def __init__(self): def __init__(self):
pygame.font.init()
self.display = pygame.display.set_mode(WINDOW_SIZE) self.display = pygame.display.set_mode(WINDOW_SIZE)
pygame.display.set_caption("Smart ForkLift")
agent_radius = int(TILE_WIDTH/2) agent_radius = int(TILE_WIDTH/2)
self.agent_tex = pygame.image.load('forklift.png') self.agent_tex = pygame.image.load('forklift.png')
self.font = pygame.font.Font('freesansbold.ttf', 16)
self.warehouse_map = warehouse.Warehouse(20, 20, 150, 20) self.warehouse_map = warehouse.Warehouse(20, 20, 150, 20)
starting_x, starting_y = self.set_starting_agent_position() starting_x, starting_y = self.set_starting_agent_position()
self.agent = agent.Agent(starting_x, starting_y, self.warehouse_map, agent_radius) self.agent = agent.Agent(starting_x, starting_y, self.warehouse_map, agent_radius)
@ -35,6 +40,7 @@ class MainGameFrame:
self.draw_floor() self.draw_floor()
self.draw_packages() self.draw_packages()
self.draw_agent() self.draw_agent()
self.draw_nums()
self.agent.move() self.agent.move()
pygame.display.update() pygame.display.update()
self.clock.tick(5) self.clock.tick(5)
@ -43,7 +49,6 @@ class MainGameFrame:
for x in range(self.warehouse_map.width): for x in range(self.warehouse_map.width):
for y in range(self.warehouse_map.height): for y in range(self.warehouse_map.height):
self.draw_field(x, y) self.draw_field(x, y)
self.draw_target(1, 1)
def draw_target(self, x, y): def draw_target(self, x, y):
target_screen_position = ( target_screen_position = (
@ -52,8 +57,6 @@ class MainGameFrame:
def draw_field(self, x, y): def draw_field(self, x, y):
current_tile = self.warehouse_map.tiles[x][y] current_tile = self.warehouse_map.tiles[x][y]
# if not isinstance(current_tile, warehouse.Tile):
# current_tile = current_tile.lays_on_field if isinstance(current_tile, warehouse.Pack) else None
color = COLOR_OF_FIELD.get(current_tile.category.name, 'white') color = COLOR_OF_FIELD.get(current_tile.category.name, 'white')
color = COLORS[color] color = COLORS[color]
if (current_tile.x_position,current_tile.y_position) in [(a.x, a.y) for a in self.agent.path]: if (current_tile.x_position,current_tile.y_position) in [(a.x, a.y) for a in self.agent.path]:
@ -77,6 +80,17 @@ class MainGameFrame:
package_color = get_package_color(pack) package_color = get_package_color(pack)
pygame.draw.rect(self.display, package_color, pygame.draw.rect(self.display, package_color,
((pack_x * TILE_WIDTH) + 3, (pack_y * TILE_HEIGHT) + 3, TILE_WIDTH - 5, TILE_HEIGHT - 5)) ((pack_x * TILE_WIDTH) + 3, (pack_y * TILE_HEIGHT) + 3, TILE_WIDTH - 5, TILE_HEIGHT - 5))
if pack.category == "freezed":
pygame.draw.rect(self.display, COLORS['blue'],
((pack_x * TILE_WIDTH) + 2, (pack_y * TILE_HEIGHT) + 2, TILE_WIDTH - 4,
TILE_HEIGHT - 4), 3)
def draw_nums(self):
for row in self.warehouse_map.tiles:
for cell in row:
if cell.category.name in self.warehouse_map.storage_types:
text_surface = self.font.render(str(cell.capacity), True, (0, 0, 0))
self.display.blit(text_surface, ((cell.x_position * TILE_WIDTH) + 6, (cell.y_position * TILE_HEIGHT) + 6))
def draw_agent(self): def draw_agent(self):
rotated = pygame.transform.rotate(self.agent_tex, DIRECTION_ANGLES.get(self.agent.direction)) rotated = pygame.transform.rotate(self.agent_tex, DIRECTION_ANGLES.get(self.agent.direction))

View File

@ -1,4 +1,4 @@
from attributes import PackSize, PackStatus from attributes import PackStatus
import random import random
import queue import queue
from collections import namedtuple from collections import namedtuple
@ -7,18 +7,17 @@ import itertools
Coordinates = namedtuple("Coordinates",'x y') Coordinates = namedtuple("Coordinates",'x y')
class CategoryData: class CategoryData:
def __init__(self, name, passable=False, can_store=True, location='general', pack_size=PackSize.ALL): def __init__(self, name, passable=False, can_store=True, location='general'):
self.name = name self.name = name
self.passable = passable self.passable = passable
self.can_store = can_store self.can_store = can_store
self.location = location self.location = location
self.pack_size = pack_size
def __repr__(self): def __repr__(self):
return self.name return self.name
class Pack: class Pack:
def __init__(self, size=PackSize.MEDIUM, categ='general', lays_on_field=None): def __init__(self, size=5, categ='general', lays_on_field=None):
self.size = size self.size = size
self.category = categ self.category = categ
assert isinstance(lays_on_field, Tile), AssertionError("Attribute lays_on_field must be a Tile object! You know, package cannot lay in void :)") assert isinstance(lays_on_field, Tile), AssertionError("Attribute lays_on_field must be a Tile object! You know, package cannot lay in void :)")
@ -27,16 +26,20 @@ class Pack:
def set_status(self): def set_status(self):
status = PackStatus.LOOSE status = PackStatus.LOOSE
if self.lays_on_field.category.name == 'Floor': if self.lays_on_field.category.name in ['Floor', 'FridgeFloor']:
status = PackStatus.LOOSE status = PackStatus.LOOSE
elif self.lays_on_field.category.name == 'Rack': elif self.lays_on_field.category.name in ['Rack', 'Fridge']:
if self.category == 'freezed' and self.lays_on_field.category.name != 'Fridge':
status = PackStatus.STORED_BAD_LOCATION
else:
status = PackStatus.STORED status = PackStatus.STORED
return status return status
CATEGORY = { CATEGORY = {
'floor': CategoryData('Floor', True, False), #lava 'floor': CategoryData('Floor', True, False), #lava
'rack': CategoryData('Rack', False, True), 'rack': CategoryData('Rack', False, True),
# 'freezer': CategoryData('Freezer', False, True, location='cold_room') 'fridge_floor': CategoryData('FridgeFloor', True, False, location='cold_room'),
'fridge': CategoryData('Fridge', False, True, location='cold_room')
} }
@ -46,9 +49,11 @@ class Warehouse:
self.height = length self.height = length
self.tiles = self.generate_map() self.tiles = self.generate_map()
self.no_of_racks = no_of_racks self.no_of_racks = no_of_racks
self.storage_types = ["Rack", "Fridge"]
self.no_of_packages = no_of_packages self.no_of_packages = no_of_packages
self.generate_racks() self.generate_racks()
self.open_isolated_areas() self.open_isolated_areas()
self.create_fridge(10)
self.packages = self.place_packages(no_of_packages) self.packages = self.place_packages(no_of_packages)
self.tiles[1][1] = Tile('floor', 1, 1) self.tiles[1][1] = Tile('floor', 1, 1)
def __str__(self): def __str__(self):
@ -68,7 +73,7 @@ class Warehouse:
node_x, node_y = random.randrange(1, self.width-1), random.randrange(1, self.height-1) node_x, node_y = random.randrange(1, self.width-1), random.randrange(1, self.height-1)
node = self.tiles[node_x][node_y] node = self.tiles[node_x][node_y]
next_node = None next_node = None
self.tiles[node_x][node_y] = Tile('rack', node_x, node_y) self.tiles[node_x][node_y] = Tile('rack', node_x, node_y, capacity=random.randrange(16, 20))
q.put(node) q.put(node)
while not q.empty(): while not q.empty():
if next_node is not None: if next_node is not None:
@ -92,6 +97,37 @@ class Warehouse:
node_y = next_node.y_position node_y = next_node.y_position
self.tiles[node_x][node_y] = Tile('rack', node_x, node_y) self.tiles[node_x][node_y] = Tile('rack', node_x, node_y)
def create_fridge(self, size):
x_corner = random.choice(['left', 'right'])
y_corner = random.choice(['top', 'bottom'])
start_x = None
start_y = None
end_x = None
end_y = None
if x_corner == 'left':
start_x = 0
end_x = size
else:
# import pdb
# pdb.set_trace()
start_x = (self.width-1) - size
end_x = self.width - 1
if y_corner == 'top':
start_y = 0
end_y = size
else:
start_y = (self.height-1) - size
end_y = self.height - 1
rows = self.tiles[start_x:end_x].copy()
for num, row in enumerate(rows, start_x):
for index, tile in enumerate(row[start_y:end_y], start_y):
if self.tiles[num][index].category.name == "Floor":
self.tiles[num][index] = Tile('fridge_floor', num, index)
else:
self.tiles[num][index] = Tile('fridge', num, index, capacity=random.randrange(10, 12))
def get_not_rack_nodes(self, node_x, node_y): def get_not_rack_nodes(self, node_x, node_y):
adjacent_tiles = self.get_adjacent_tiles(node_x, node_y) adjacent_tiles = self.get_adjacent_tiles(node_x, node_y)
possible_nodes = [line for line in adjacent_tiles if line.category.name != "Rack"] possible_nodes = [line for line in adjacent_tiles if line.category.name != "Rack"]
@ -155,18 +191,27 @@ class Warehouse:
racks.append(y) racks.append(y)
return racks return racks
def place_packages(self, no_of_packages: int):
def place_packages(self, no_of_packages):
packages = [] packages = []
for i in range(no_of_packages): for i in range(no_of_packages):
pack_x, pack_y = self._set_package_position() new_package_size = random.randrange(1, 10)
pack_x, pack_y = self._set_package_position(new_package_size)
package_field = self.tiles[pack_x][pack_y] package_field = self.tiles[pack_x][pack_y]
new_package = Pack(lays_on_field=package_field) new_package = Pack(lays_on_field=package_field)
categ_seed = random.random()
if categ_seed > 0.8:
new_package.category = "freezed"
# new_package.set_status()
new_package.size = new_package_size
if package_field.category.name in self.storage_types:
package_field.capacity -= new_package.size
packages.append(new_package) packages.append(new_package)
return packages return packages
def _set_package_position(self):
def _set_package_position(self, pack_size: int):
starting_x, starting_y = random.randrange(self.width), random.randrange(self.height) starting_x, starting_y = random.randrange(self.width), random.randrange(self.height)
while not isinstance(self.tiles[starting_x][starting_y], Tile): while not isinstance(self.tiles[starting_x][starting_y], Tile) \
and self.tiles[starting_x][starting_y].size - pack_size < 0:
starting_x, starting_y = random.randrange(self.width), random.randrange( starting_x, starting_y = random.randrange(self.width), random.randrange(
self.height) self.height)
return starting_x, starting_y return starting_x, starting_y
@ -202,10 +247,11 @@ class Warehouse:
return wall return wall
class Tile: class Tile:
def __init__(self, category, x_position, y_position): def __init__(self, category, x_position, y_position, capacity=10):
self.category = CATEGORY.get(category, CATEGORY['floor']) self.category = CATEGORY.get(category, CATEGORY['floor'])
self.x_position = x_position self.x_position = x_position
self.y_position = y_position self.y_position = y_position
self.capacity = capacity
def __str__(self): def __str__(self):