parent
ae14326fe8
commit
4c59532a9f
6 changed files with 583 additions and 0 deletions
After Width: | Height: | Size: 2.3 KiB |
@ -0,0 +1,341 @@ |
||||
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) |
@ -0,0 +1,30 @@ |
||||
import pygame |
||||
|
||||
from settings import Settings |
||||
from spritesheet import SquareSpriteSheet |
||||
from ship import Drone |
||||
|
||||
settings = Settings() |
||||
|
||||
screen = pygame.display.set_mode((settings.width, settings.height)) |
||||
pygame.display.set_caption(settings.caption) |
||||
|
||||
pygame.init() |
||||
|
||||
ship = Drone(screen, (0, 0)) |
||||
|
||||
run = True |
||||
|
||||
while run: |
||||
#update background |
||||
screen.fill(settings.background_color) |
||||
|
||||
ship.update() |
||||
|
||||
#event handler |
||||
for event in pygame.event.get(): |
||||
if event.type == pygame.QUIT: |
||||
run = False |
||||
|
||||
pygame.display.update() |
||||
|
@ -0,0 +1,6 @@ |
||||
class Settings(): |
||||
def __init__(self) -> None: |
||||
self.width = 500 |
||||
self.height = 500 |
||||
self.caption = "My Game" |
||||
self.background_color = (50, 50, 50) |
@ -0,0 +1,172 @@ |
||||
import pygame |
||||
from enum import Enum |
||||
import math |
||||
import random |
||||
|
||||
from spritesheet import SquareSpriteSheet |
||||
|
||||
class DroneStatus(Enum): |
||||
GROUNDED = 0 |
||||
TAKINGOFF = 1 |
||||
TURNING = 2 |
||||
INTHEAIR = 3 |
||||
LANDING = 4 |
||||
|
||||
class Drone: |
||||
def __init__(self, screen, pos = (0, 0)) -> None: |
||||
|
||||
# image |
||||
self.screen = screen |
||||
self.sheet = sheet = SquareSpriteSheet('assets/ship-sheet.png', 96, 4) |
||||
self.scale = 1 |
||||
self.angle = 0 |
||||
|
||||
# animation |
||||
self.frame = 0 |
||||
self.animation_cooldown = 75 |
||||
|
||||
# tracking |
||||
self.scale = .25 |
||||
self.last_update = 0 |
||||
self.pos = pos |
||||
self.prev_index = 0 |
||||
self.next_index = 1 |
||||
|
||||
self.route = [(400,400),(0,400),(400,0),(0,0),(400,0)] |
||||
|
||||
# status |
||||
self.status = DroneStatus.TAKINGOFF |
||||
self.animate = False |
||||
self.last_departure = 0 |
||||
|
||||
self.rate = 100/1000 #(units per second) |
||||
|
||||
def update(self): |
||||
|
||||
current_time = pygame.time.get_ticks() |
||||
|
||||
if(self.status == DroneStatus.GROUNDED): |
||||
self.prev_index = 0 |
||||
self.next_index = 1 |
||||
|
||||
self.scale = .25 |
||||
self.animate = False |
||||
|
||||
elif(self.status == DroneStatus.TAKINGOFF): |
||||
self.scale += .001 |
||||
|
||||
if(self.scale >= 1): |
||||
self.scale = 1 |
||||
self.status = DroneStatus.TURNING |
||||
|
||||
self.animate = True |
||||
elif(self.status == DroneStatus.TURNING): |
||||
x1 = self.route[self.prev_index][0] |
||||
y1 = self.route[self.prev_index][1] |
||||
|
||||
x2 = self.route[self.next_index][0] |
||||
y2 = self.route[self.next_index][1] |
||||
|
||||
#https://replit.com/@Rabbid76/PyGame-RotateWithMouse#main.py |
||||
|
||||
correction_angle = 90 |
||||
dx = x2 - x1 |
||||
dy = y2 - y1 |
||||
new_angle = math.degrees(math.atan2(-dy, dx)) - correction_angle |
||||
|
||||
if(int(self.angle) > int(new_angle)): |
||||
self.angle -= .1 |
||||
elif(int(self.angle) < int(new_angle)): |
||||
self.angle += .1 |
||||
|
||||
if((int(self.angle) + int(new_angle)) == 0): |
||||
self.angle = new_angle |
||||
self.last_departure = current_time |
||||
self.status = DroneStatus.INTHEAIR |
||||
|
||||
if((int(self.angle) - int(new_angle)) == 0): |
||||
self.angle = new_angle |
||||
self.last_departure = current_time |
||||
self.status = DroneStatus.INTHEAIR |
||||
|
||||
self.animate = True |
||||
|
||||
elif(self.status == DroneStatus.INTHEAIR): |
||||
|
||||
x1 = self.route[self.prev_index][0] |
||||
y1 = self.route[self.prev_index][1] |
||||
|
||||
x2 = self.route[self.next_index][0] |
||||
y2 = self.route[self.next_index][1] |
||||
|
||||
going_north = y2 >= y1 |
||||
going_east = x2 >= x1 |
||||
|
||||
slope = 0 |
||||
|
||||
if(x2 - x1 != 0): |
||||
slope = (y2 - y1) / (x2 - x1) |
||||
|
||||
# total distance: d=√((x2 – x1)² + (y2 – y1)²) |
||||
total_distance = (math.sqrt(abs(((x2 - x1) * (x2 - x1)) + ((y2 - y1) * (y2 - y1))))) |
||||
|
||||
# d = rt |
||||
distance_traveled = self.rate * (current_time - self.last_departure) |
||||
|
||||
# if traveled the distance, ground the drone and update stops |
||||
if(distance_traveled >= total_distance): |
||||
# force position |
||||
self.pos = self.route[self.next_index] |
||||
|
||||
self.status = DroneStatus.LANDING |
||||
else: |
||||
# what percentage of the journey has passed? |
||||
per = (distance_traveled/total_distance) |
||||
|
||||
# compute X |
||||
if(going_east): |
||||
x = x1 + ((x2 - x1) * per) |
||||
else: |
||||
x = x1 - ((x1 - x2) * per) |
||||
|
||||
# compute y |
||||
if(slope > 0): |
||||
# if there is a slope |
||||
#(y – y1) = m(x – x1) |
||||
y = (slope * (x - x1)) + y1 |
||||
else: |
||||
if(going_north): |
||||
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.pos = (x, y) |
||||
self.scale = 1 |
||||
self.animate = True |
||||
|
||||
elif(self.status == DroneStatus.LANDING): |
||||
self.scale -= .001 |
||||
|
||||
if(self.scale <= .25): |
||||
self.scale = .25 |
||||
self.status = DroneStatus.TAKINGOFF |
||||
self.prev_index = self.next_index |
||||
self.next_index = self.prev_index + 1 if self.prev_index + 1 < len(self.route) else 0 |
||||
|
||||
self.animate = True |
||||
|
||||
if(self.animate): |
||||
if current_time - self.last_update >= self.animation_cooldown: |
||||
self.frame += 1 |
||||
self.frame = self.frame if self.frame < len(self.sheet.images) else 0 |
||||
self.last_update = current_time |
||||
|
||||
current_image = self.sheet.get_image_by_frame(self.frame, self.scale, self.angle) |
||||
self.screen.blit(current_image, (self.pos)) |
@ -0,0 +1,34 @@ |
||||
import pygame |
||||
|
||||
class SquareSpriteSheet(): |
||||
def __init__(self, filename, size, numberOfImages, transparency_color=(0,0,0)) -> None: |
||||
self.sheet = pygame.image.load(filename).convert_alpha() |
||||
self.size = size |
||||
self.numberOfImages = numberOfImages |
||||
self.transparency_color = transparency_color |
||||
self.images = [] |
||||
|
||||
self.load_images() |
||||
|
||||
def load_images(self): |
||||
for i in range(self.numberOfImages): |
||||
image = pygame.Surface((self.size, self.size)).convert_alpha() |
||||
image.blit(self.sheet, (0,0), ((i * self.size), 0, self.size, self.size)) |
||||
image.set_colorkey(self.transparency_color) |
||||
self.images.append(image) |
||||
|
||||
def get_image_by_frame(self, frame, scale=1, angle=0): |
||||
image = self.images[frame] |
||||
image = pygame.transform.scale(image, (self.size * scale, self.size * scale)) |
||||
image = self.rot_center(image, angle) |
||||
image.set_colorkey(self.transparency_color) |
||||
return image |
||||
|
||||
def rot_center(self, image, angle): |
||||
"""rotate an image while keeping its center and size""" |
||||
orig_rect = image.get_rect() |
||||
rot_image = pygame.transform.rotate(image, angle) |
||||
rot_rect = orig_rect.copy() |
||||
rot_rect.center = rot_image.get_rect().center |
||||
rot_image = rot_image.subsurface(rot_rect).copy() |
||||
return rot_image |
Loading…
Reference in new issue