From 4c59532a9f74145632b3f038bed763ea3ae2f532 Mon Sep 17 00:00:00 2001 From: mkendrick Date: Sun, 27 Aug 2023 17:33:39 -0400 Subject: [PATCH] base --- assets/ship-sheet.png | Bin 0 -> 2333 bytes factory.py | 341 ++++++++++++++++++++++++++++++++++++++++++ game.py | 30 ++++ settings.py | 6 + ship.py | 172 +++++++++++++++++++++ spritesheet.py | 34 +++++ 6 files changed, 583 insertions(+) create mode 100644 assets/ship-sheet.png create mode 100644 factory.py create mode 100644 game.py create mode 100644 settings.py create mode 100644 ship.py create mode 100644 spritesheet.py diff --git a/assets/ship-sheet.png b/assets/ship-sheet.png new file mode 100644 index 0000000000000000000000000000000000000000..6c3ea783c704e55bac021a0c130e123813802a2a GIT binary patch literal 2333 zcmb_edpy(q9{+7ALLzeODLRttDi2bIxzps*^a#_e$LU!mmWH{cjY5jHO1iR@G+{1j zlMp(`C4^kdPA(71eZ(TRdA`5%|9PE1&iUv2`n`VJ@AJNWKA+z&&Bx1ai@dfx1VLMl zxI5z@2tfzeM7d4iR9nhRgCM!FBhC)TV-T}n%5Lm((QH~kFVIRmeDh~q9vuO!&7%9J{=K0>}FgY zMj=G#H81qMmv@RQoq=BdYgc3vU~cM7-$;z5xHf`H_UZ_Ax@r19(3i58Z`@N# znwv{jcv6%syDUuAArBssJDnpZuU3qd?kk?DUm+PpMZMk(pn@u7>f!3PNK;7ErmyD! zA!LG(-c?T(HeX)jX`szN>ap7)<8rxSR&p8DU}gDvYgg%BS~L_8zS$c7bCy9Jvb6fJ zUWkmcz3z=VLpS@?xmau##Wt@z)5ceks~MV3d9dHs+l0ogXtQ)}rF>|>eLrqO{;WM3 z^wm#F8Go--_fTa@#U4s*+2{`$vxq6FK)OHj^G$lB0!2J=?DZfOgHb$uxtfC zw*;>?1rHp|D+)V7Q|tS@&b09mI2NR}yNu^A}DzBgN5tkPmxcyJ|bb2d>iux z%rz{<&xz}86p9!1^PRr;#P-;E7)}Sp2u;EYQwAf>;zcK{YO<`n$##A=XviZa4#;6& z1eUiL+#lHu+9->@Cg)y(^5YchWzXLZPj(zHU=I!EiHuH9`y1tP8x`MoU+hHX5JJ;m z-n~LF8E1^*RzKRMuHgH%ZA%%~FB|ZhEQxh(L_erpzC0}L*ZPI< zRZ8Abe6u@#Xnh76$mxM6WXEn0p-Oj~E*t_M#ylv97iG_nPLpR+U#h>A{;4Zo88PrY ze7sn`!$u(C60~!L>G&oU?UK`Bg`z;O_w{JPEapT&Ma{!yG(A+u%` zxjnpkuL$KWcTMfpEUT&MRWr`taiXsL*Zo*ry8V@}!!0!$~1h8fpzB{DV0Z zYPmGX9N>@9XrB-uiwN$_p*%87fAiA1N9y2~K{g?q!GSu6`<+uA1_^da{e}*Yk*6Bly|X^bI2~if7fR zwHPI&3~!b<`Zhu{JT z7XZTf(dV=@f#1p+2N?7vcV|Xu`^@95w&!Z>4yz)iCN}-z6}<0JqX2KIm^ok8F>r9a z{E!8UQsvoBr9R@^w{T^h!I@-~O<6V?hvK6eX{eOisBcBnmFuh(i`rJacx_QeUkoB< zvw_KS=d&P%x<5Wx<-+hub?AusSXVEVL}S_ z4tE@k0YF_Q0PsCWLBZ^F5sO>vvCSV>yGYZN%hjy@WA8LQ+7auqBw3ocNu4NrfPX-u z2i!@PFNM^s&7LI#qta16ODaqyLhXqAG_T04??=F>5l11P+lvjEugF~OE#&rIX&fdS zi3(5P3uQ9`u4B+p=)o6o8yahXR4*lI?E<8To7ECz#=hVt`5uD6D;&Fh?38n%ye`eK z&<0-}4Id{SFYs(SpZDhrhC=?6fk3W>s~~Vezo&eI;VJcI1W 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) diff --git a/game.py b/game.py new file mode 100644 index 0000000..180d8fe --- /dev/null +++ b/game.py @@ -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() + diff --git a/settings.py b/settings.py new file mode 100644 index 0000000..67ba557 --- /dev/null +++ b/settings.py @@ -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) \ No newline at end of file diff --git a/ship.py b/ship.py new file mode 100644 index 0000000..5b4c37e --- /dev/null +++ b/ship.py @@ -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)) \ No newline at end of file diff --git a/spritesheet.py b/spritesheet.py new file mode 100644 index 0000000..ddcfd4f --- /dev/null +++ b/spritesheet.py @@ -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 \ No newline at end of file