From 90e421909a35fcfe2c76902e7557cbab94d7f7ac Mon Sep 17 00:00:00 2001 From: nejdetckenobi Date: Sat, 10 Oct 2026 19:27:52 +0300 Subject: [PATCH] Planet and spaceship creation, travel and destruction --- .gitignore | 1 + components.py | 67 +++++++++++++++++++++++++++++++++ helpers.py | 96 ++++++++++++++++++++++++++++++++++++++++++++++++ requirements.txt | 1 + settings.py | 11 ++++++ systems.py | 64 ++++++++++++++++++++++++++++++++ ui.py | 67 +++++++++++++++++++++++++++++++++ 7 files changed, 307 insertions(+) create mode 100644 components.py create mode 100644 helpers.py create mode 100644 requirements.txt create mode 100644 settings.py create mode 100644 systems.py create mode 100644 ui.py diff --git a/.gitignore b/.gitignore index 726a190..bc1566d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +.vscode .venv *.pyc **__pycache__/ diff --git a/components.py b/components.py new file mode 100644 index 0000000..fbf4581 --- /dev/null +++ b/components.py @@ -0,0 +1,67 @@ +from dataclasses import dataclass, field +from random import randrange + + +def default_rotation(): + return randrange(0, 359) + + +@dataclass +class Vector2D: + x: float + y: float + + def __iadd__(self, other): + self.x += other.x + self.y += other.y + return self + + def __add__(self, other): + return Vector2D( + x=self.x + other.x, + y=self.y + other.y, + ) + + def __sub__(self, other): + return Vector2D( + x=self.x - other.x, + y=self.y - other.y, + ) + + def __abs__(self): + return (self.x * self.x + self.y * self.y) ** 0.5 + + +@dataclass +class Position(Vector2D): + pass + + +@dataclass +class Target(Vector2D): + pass + + +@dataclass +class Speed(Vector2D): + pass + + +@dataclass +class Color: + value: tuple[int, int, int, int] + + +@dataclass +class Radius: + value: int + + +@dataclass +class SpaceShipMarker: + pass + + +@dataclass +class PlanetMarker: + pass diff --git a/helpers.py b/helpers.py new file mode 100644 index 0000000..119c654 --- /dev/null +++ b/helpers.py @@ -0,0 +1,96 @@ +from pyglet.shapes import Circle, Line +from libs.ecs import World +from components import ( + Color, + PlanetMarker, + Position, + Radius, + SpaceShipMarker, + Target, +) +from random import randint, sample +from settings import ( + MIN_PLANET_RADIUS, + MAX_PLANET_RADIUS, + SCREEN_WIDTH, + SCREEN_HEIGHT, + SPACESHIP_COLOR, + SPACESHIP_PATH_COLOR, + SPACESHIP_RADIUS, +) + + +def create_planet( + world: World, + x: int, + y: int, + radius: int, + color: tuple[int, int, int, int], +) -> int: + return world.spawn( + PlanetMarker(), + Position(x=x, y=y), + Color(value=color), + Circle(x=x, y=y, radius=radius, color=color), + Radius(radius), + ) + + +def create_spaceship( + world: World, + src_x: int, + src_y: int, + dest_x: int, + dest_y: int, + approach_radius: int, +): + return world.spawn( + SpaceShipMarker(), + Position(x=src_x, y=src_y), + Target(x=dest_x, y=dest_y), + Radius(approach_radius), + Circle(x=src_x, y=src_y, radius=SPACESHIP_RADIUS, color=SPACESHIP_COLOR), + Line(x=src_x, y=src_y, x2=dest_x, y2=dest_y, color=SPACESHIP_PATH_COLOR), + ) + + +def generate_random_planet(world: World) -> int: + radius: int = randint(MIN_PLANET_RADIUS, MAX_PLANET_RADIUS) + x: int = randint(radius, SCREEN_WIDTH - radius) + y: int = randint(radius, SCREEN_HEIGHT - radius) + color = (randint(0, 255), randint(0, 255), randint(0, 255), 255) + + return create_planet(world, x, y, radius, color) + + +def create_travelling_spaceship( + world: World, + src_planet: int, + dest_planet: int, +): + src_planet_cs = world.get_components(src_planet) + if src_planet_cs is None: + raise Exception("invalid_planet") + dest_planet_cs = world.get_components(dest_planet) + if dest_planet_cs is None: + raise Exception("invalid_planet") + + source_pos = src_planet_cs[Position] + dest_pos = dest_planet_cs[Position] + approach_radius = dest_planet_cs[Radius].value + return create_spaceship( + world, + src_x=source_pos.x, + src_y=source_pos.y, + dest_x=dest_pos.x, + dest_y=dest_pos.y, + approach_radius=approach_radius, + ) + + +def generate_random_spaceship(world: World) -> int: + planets = [entity for entity, _ in world.query(PlanetMarker)] + + _planet_sample = sample(planets, 2) + + return create_travelling_spaceship(world, *_planet_sample) diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..b5f1cd7 --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +pyglet \ No newline at end of file diff --git a/settings.py b/settings.py new file mode 100644 index 0000000..c77b2a1 --- /dev/null +++ b/settings.py @@ -0,0 +1,11 @@ +FPS = 60 + +SCREEN_WIDTH = 1280 +SCREEN_HEIGHT = 1024 +FULLSCREEN = False +MAX_PLANET_RADIUS = 50 +MIN_PLANET_RADIUS = 20 +SPACESHIP_RADIUS = 5 +SPACESHIP_COLOR = (255, 255, 255, 255) +SPACESHIP_PATH_COLOR = (127, 127, 127, 127) +SPACESHIP_SPEED = 500 diff --git a/systems.py b/systems.py new file mode 100644 index 0000000..5f01d62 --- /dev/null +++ b/systems.py @@ -0,0 +1,64 @@ +from pyglet.shapes import Circle + +from libs.ecs import World +from components import ( + PlanetMarker, + Position, + Color, + Radius, + SpaceShipMarker, + Target, + Vector2D, +) +from settings import SPACESHIP_COLOR, SPACESHIP_RADIUS, SPACESHIP_SPEED + + +class PlanetRenderSyncSystem: + @classmethod + def process(cls, world: World, **args): + for _, component_storage in world.query(PlanetMarker): + pos: Position = component_storage[Position] + color: Color = component_storage[Color] + radius: Radius = component_storage[Radius] + circle: Circle = component_storage[Circle] + circle.radius = radius.value + circle.x, circle.y = pos.x, pos.y + circle.color = color.value + + +class SpaceShipRenderSyncSystem: + @classmethod + def process(cls, world: World, **args): + for _, component_storage in world.query(SpaceShipMarker): + pos: Position = component_storage[Position] + circle: Circle = component_storage[Circle] + circle.radius = SPACESHIP_RADIUS + circle.x, circle.y = pos.x, pos.y + circle.color = SPACESHIP_COLOR + + +class SpaceShipKillSystem: + @classmethod + def process(cls, world: World, **args): + marked_for_death = [] + for entity, component_storage in world.query(SpaceShipMarker): + pos: Position = component_storage[Position] + target: Target = component_storage[Target] + approach_radius = component_storage[Radius] + if abs(pos - target) < approach_radius.value: + marked_for_death.append(entity) + for entity in marked_for_death: + world.get_components(entity=entity, pop=True) + + +class SpaceShipTravelSystem: + @classmethod + def process(cls, world: World, **args): + for _, component_storage in world.query(SpaceShipMarker): + pos: Position = component_storage[Position] + target: Target = component_storage[Target] + + diff: Vector2D = target - pos + diff_mag = abs(diff) + pos.x = pos.x + args["dt"] * SPACESHIP_SPEED * diff.x / diff_mag + pos.y = pos.y + args["dt"] * SPACESHIP_SPEED * diff.y / diff_mag diff --git a/ui.py b/ui.py new file mode 100644 index 0000000..7ddf106 --- /dev/null +++ b/ui.py @@ -0,0 +1,67 @@ +from functools import partial +from random import randint + +import pyglet +from pyglet.shapes import Circle, Line +from components import PlanetMarker, SpaceShipMarker +from helpers import generate_random_planet, generate_random_spaceship +from libs.ecs import World +from systems import ( + PlanetRenderSyncSystem, + SpaceShipKillSystem, + SpaceShipRenderSyncSystem, + SpaceShipTravelSystem, +) +from settings import FPS, FULLSCREEN, SCREEN_WIDTH, SCREEN_HEIGHT + +world = World() +world.systems.extend( + ( + SpaceShipKillSystem, + PlanetRenderSyncSystem, + SpaceShipRenderSyncSystem, + SpaceShipTravelSystem, + ) +) + +window = pyglet.window.Window( + caption="SpaceTrader", + width=SCREEN_WIDTH, + height=SCREEN_HEIGHT, + fullscreen=FULLSCREEN, +) + +for _ in range(12): + generate_random_planet(world) + +for i in range(10): + generate_random_spaceship(world) + + +@window.event +def on_draw(): + window.clear() + for _, component_storage in world.query(SpaceShipMarker): + component_storage[Line].draw() + + for _, component_storage in world.query(PlanetMarker): + component_storage[Circle].draw() + + for _, component_storage in world.query(SpaceShipMarker): + component_storage[Circle].draw() + + +def on_update(dt): + world.tick(dt=dt) + + +def periodic_random_spaceship_generation(*args, **kwargs): + for i in range(randint(1, 5)): + generate_random_spaceship(world=world) + + +pyglet.clock.schedule_interval(on_update, 1 / FPS) + +pyglet.clock.schedule_interval(periodic_random_spaceship_generation, 0.5) + +pyglet.app.run()