You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
341 lines
11 KiB
341 lines
11 KiB
import time, datetime, math
|
|
from copy import deepcopy
|
|
from enum import Enum
|
|
|
|
def now():
|
|
return datetime.datetime.now().timestamp()
|
|
|
|
class DroneState(Enum):
|
|
Grounded = 0
|
|
InTheAir = 1
|
|
WaitingForTakeOff = 2
|
|
|
|
class StopType(Enum):
|
|
City = 0
|
|
Factory = 1
|
|
|
|
class Position:
|
|
def __init__(self, x, y):
|
|
self.x = x
|
|
self.y = y
|
|
def __str__(self):
|
|
return(f"[{self.x}, {self.y}]")
|
|
|
|
class Drone:
|
|
def __init__(self, name, position, route, rate, capacity):
|
|
self.__name = name
|
|
self.__position = position
|
|
self.__route = route
|
|
self.__rate = rate # per second
|
|
self.__capacity = capacity
|
|
self.__cargo = {}
|
|
|
|
self.__state = DroneState.Grounded
|
|
self.__prevousStop = -1
|
|
self.__nextStop = 0
|
|
self.__lastDeparture = 0
|
|
self.__lastDeparturePosition = deepcopy(position)
|
|
|
|
def set_position(self, position):
|
|
self.__position = position
|
|
|
|
# returns how much was accepted from the load
|
|
def load_cargo(self, product, quantity):
|
|
cargoTotal = self.get_cargo_total()
|
|
|
|
if not product in self.__cargo:
|
|
self.__cargo[product] = 0
|
|
|
|
spaceRemaining = (self.__capacity - cargoTotal)
|
|
|
|
acceptedQuantity = quantity
|
|
|
|
# if the quantity is more than space remaining then just return the space remaining
|
|
if(quantity > spaceRemaining):
|
|
acceptedQuantity = spaceRemaining
|
|
|
|
self.__cargo[product] += acceptedQuantity
|
|
|
|
return acceptedQuantity
|
|
|
|
def get_cargo_total(self):
|
|
total = 0
|
|
|
|
for key in self.__cargo:
|
|
total += self.__cargo[key]
|
|
|
|
return total
|
|
|
|
def takeOff(self):
|
|
self.__state = DroneState.InTheAir
|
|
self.__lastDeparture = now()
|
|
self.__lastDeparturePosition = deepcopy(self.__position)
|
|
|
|
positionFound = False
|
|
|
|
for idx, stop in enumerate(self.__route.get_stops()):
|
|
stopPosition = stop.get_position()
|
|
|
|
if (stopPosition.x == self.__position.x) and (stopPosition.y == self.__position.y):
|
|
positionFound = True
|
|
|
|
self.__prevousStop = idx
|
|
|
|
# if the next stop is out of range, loop back home
|
|
if(idx + 1 == len(self.__route.get_stops())):
|
|
self.__nextStop = 0
|
|
else:
|
|
self.__nextStop = idx + 1
|
|
|
|
if(not positionFound):
|
|
self.__prevousStop = -1
|
|
self.__nextStop = 0
|
|
print(f"{self.__name}: Taking off from: {self.__position}")
|
|
else:
|
|
print(f"{self.__name}: Taking off from: {self.__route.get_stops()[self.__prevousStop]}")
|
|
|
|
def cargo_report(self):
|
|
str = ""
|
|
for product in self.__cargo:
|
|
str += f"{product}: {self.__cargo[product]}"
|
|
return
|
|
|
|
def update(self):
|
|
|
|
state = DroneState.Grounded.name
|
|
position = f"Position: {self.__position}"
|
|
prevousStop = self.__route.get_stops()[self.__prevousStop]
|
|
nextStop = self.__route.get_stops()[self.__nextStop]
|
|
|
|
# print cargo levels
|
|
print(f"{self.__name}: On Board {self.__cargo}")
|
|
|
|
# are we at a unknown location?
|
|
if(self.__prevousStop == -1):
|
|
location = self.__lastDeparturePosition
|
|
else:
|
|
location = prevousStop
|
|
|
|
# is the drone in the air?
|
|
if(self.__state == DroneState.Grounded):
|
|
print(f"{self.__name}: {state}: {location}")
|
|
elif(self.__state == DroneState.WaitingForTakeOff):
|
|
currentStop = prevousStop
|
|
|
|
print(f"{self.__name}: Waiting For Take Off: {location}")
|
|
|
|
# load cargo/receive_supplies if at a factory
|
|
if(currentStop.get_stop_type() == StopType.Factory):
|
|
print(f"{self.__name}: Loading Inventory")
|
|
|
|
products = currentStop.get_output_quantity()
|
|
|
|
for key in products:
|
|
acceptedQuantity = self.load_cargo(key, products[key])
|
|
|
|
# adjust inventory by accepted quantity
|
|
currentStop.update_inventory(-acceptedQuantity)
|
|
|
|
currentStop.receive_supplies(self.__cargo)
|
|
|
|
if(self.__nextTakeOff <= now()):
|
|
self.takeOff()
|
|
else:
|
|
print(f"{self.__name}: Traveling Between: {location} and {nextStop}")
|
|
|
|
# time in the air since take off
|
|
timeDifference = math.floor(now() - self.__lastDeparture)
|
|
|
|
x2 = float(nextStop.get_position().x)
|
|
y2 = float(nextStop.get_position().y)
|
|
|
|
if(self.__prevousStop == -1):
|
|
x1 = float(self.__lastDeparturePosition.x)
|
|
y1 = float(self.__lastDeparturePosition.y)
|
|
else:
|
|
x1 = prevousStop.get_position().x
|
|
y1 = prevousStop.get_position().y
|
|
|
|
#print(f"{self.__name}: {x1},{y1} -> {x2},{y2}")
|
|
|
|
# total distance: d=√((x2 – x1)² + (y2 – y1)²)
|
|
totalDistance = (math.sqrt(abs(((x2 - x1) * (x2 - x1)) + ((y2 - y1) * (y2 - y1)))))
|
|
|
|
# d = rt
|
|
distanceTraveled = self.__rate * (now() - self.__lastDeparture)
|
|
|
|
#print(f"{self.__name}: Distance Traveled: {distanceTraveled}, Total Distance: {totalDistance}")
|
|
|
|
# slope m = (y2 - y1) / (x2 - x1)
|
|
if(x2 == x1):
|
|
m = 0
|
|
else:
|
|
m = (y2 - y1) / (x2 - x1)
|
|
|
|
travelingEast = x2 > x1
|
|
travelingNorth = y2 > y1
|
|
|
|
# if traveled the distance, ground the drone and update stops
|
|
if(distanceTraveled >= totalDistance):
|
|
# force position
|
|
self.__position.x = nextStop.get_position().x
|
|
self.__position.y = nextStop.get_position().y
|
|
|
|
self.__state = DroneState.WaitingForTakeOff
|
|
self.__nextTakeOff = now() + 5
|
|
self.__prevousStop = self.__nextStop
|
|
self.__nextStop += 1
|
|
|
|
# if next stop out of range, sent back to first stop
|
|
if(self.__nextStop >= len(self.__route.get_stops())):
|
|
self.__nextStop = 0
|
|
else:
|
|
# what percentage of the journey has passed?
|
|
per = (distanceTraveled/totalDistance)
|
|
|
|
# compute X
|
|
if(travelingEast):
|
|
x = x1 + ((x2 - x1) * per)
|
|
else:
|
|
x = x1 - ((x1 - x2) * per)
|
|
|
|
# compute y
|
|
if(m > 0):
|
|
# if there is a slope
|
|
#(y – y1) = m(x – x1)
|
|
y = (m * (x - x1)) + y1
|
|
else:
|
|
if(travelingNorth):
|
|
y = y1 + ((y2 - y1) * per)
|
|
else:
|
|
y = y1 - ((y1 - y2) * per)
|
|
|
|
# if no slope, either x or y is constant
|
|
if(x1 == x2):
|
|
x = x1
|
|
|
|
if(y1 == y2):
|
|
y = y1
|
|
|
|
self.__position.x = x
|
|
self.__position.y = y
|
|
|
|
print(f"{self.__name} {position}")
|
|
def get_state(self):
|
|
return self.__state
|
|
|
|
class Route:
|
|
def __init__(self, name, stops):
|
|
self.__name = name
|
|
self.__stops = stops
|
|
def get_stops(self):
|
|
return self.__stops
|
|
|
|
class Product():
|
|
def __init__(self, name):
|
|
self.__name = name
|
|
def __str__(self):
|
|
return f"{self.__name}"
|
|
def get_name(self):
|
|
return self.__name
|
|
|
|
class Stop:
|
|
def __init__(self, stopType, name, position):
|
|
self.__stopType = stopType
|
|
self.__name = name
|
|
self.__position = position
|
|
def __str__(self):
|
|
return f"{self.__name} {self.__position}"
|
|
def get_position(self):
|
|
return self.__position
|
|
def get_name(self):
|
|
return self.__name
|
|
def get_stop_type(self):
|
|
return self.__stopType
|
|
|
|
class Factory(Stop):
|
|
def __init__(self, name, position, product, buildRate, acceptedSupplies):
|
|
super().__init__(StopType.Factory, name, position)
|
|
|
|
#product name
|
|
self.__product = product
|
|
#build rate (units per second)
|
|
self.__buildRate = buildRate
|
|
#units ready to ship
|
|
self.__inventory = {product: 0}
|
|
#last time inventory was updated
|
|
self.__lastCheck = 0
|
|
#acceptedSupplies = products accepted by this factory
|
|
self.__acceptedSupplies = acceptedSupplies
|
|
|
|
# returns a dictionary of the product and inventory level
|
|
def get_output_quantity(self):
|
|
# only return finished goods
|
|
return {self.__product: math.floor(self.__inventory[self.__product])}
|
|
|
|
# adds to inventory level
|
|
def update_inventory(self, quantity):
|
|
self.__inventory[self.__product] += quantity
|
|
|
|
def update_supply_inventory(self, product, quantity):
|
|
if product not in self.__inventory:
|
|
self.__inventory[product] = 0
|
|
|
|
self.__inventory[product] += quantity
|
|
|
|
def receive_supplies(self, supplies):
|
|
for supply in supplies:
|
|
if supply in self.__acceptedSupplies:
|
|
self.update_supply_inventory(supply, supplies[supply])
|
|
|
|
def inventory_report(self):
|
|
str = f"{self.get_name()}: "
|
|
for product in self.__inventory:
|
|
str += f"{product}: {self.__inventory[product]} "
|
|
return str
|
|
|
|
# runs on timer tick
|
|
def update(self):
|
|
now = datetime.datetime.now().timestamp()
|
|
|
|
if(self.__lastCheck > 0):
|
|
timeDifference = (now - self.__lastCheck)
|
|
self.update_inventory(timeDifference * self.__buildRate)
|
|
|
|
print(self.inventory_report())
|
|
|
|
self.__lastCheck = datetime.datetime.now().timestamp()
|
|
|
|
chicago = Stop(StopType.City, "Chicago", Position(1, 45))
|
|
miami = Stop(StopType.City, "Miami", Position(50, 1))
|
|
newYork = Stop(StopType.City, "New York", Position(50, 50))
|
|
|
|
widget = Product("Widget")
|
|
gizmo = Product("Gizmo")
|
|
|
|
widgetFactory = Factory("Widget Inc", Position(50, 25), widget, .25, [])
|
|
|
|
gizmoSupplies = [widget]
|
|
gizmoFactory = Factory("Gizmo Inc", Position(50, 30), gizmo, .25, gizmoSupplies)
|
|
|
|
factories = [widgetFactory, gizmoFactory]
|
|
|
|
route = Route(name="Express Route", stops=(widgetFactory, gizmoFactory))
|
|
routeRev = Route(name="Express Route Reverse", stops=(miami, newYork, chicago, widgetFactory))
|
|
|
|
alphaDrone = Drone("Alpha", Position(25, 25), route, 1, 10)
|
|
betaDrone = Drone("Beta", Position(35, 25), routeRev, 5, 10)
|
|
|
|
drones = [alphaDrone]
|
|
|
|
while(True):
|
|
for drone in drones:
|
|
if(drone.get_state() == DroneState.Grounded):
|
|
drone.takeOff()
|
|
else:
|
|
drone.update()
|
|
for factory in factories:
|
|
factory.update()
|
|
pass
|
|
|
|
time.sleep(1)
|
|
|