aboutsummaryrefslogtreecommitdiff
path: root/kennel/engine
diff options
context:
space:
mode:
Diffstat (limited to 'kennel/engine')
-rw-r--r--kennel/engine/__init__.py0
-rw-r--r--kennel/engine/components/__init__.py0
-rw-r--r--kennel/engine/components/component.py16
-rw-r--r--kennel/engine/components/controllable.py13
-rw-r--r--kennel/engine/components/position.py14
-rw-r--r--kennel/engine/entities/__init__.py0
-rw-r--r--kennel/engine/entities/cat.py33
-rw-r--r--kennel/engine/entities/entity.py60
-rw-r--r--kennel/engine/entities/laser.py13
-rw-r--r--kennel/engine/game.py53
-rw-r--r--kennel/engine/systems/__init__.py0
-rw-r--r--kennel/engine/systems/network.py88
-rw-r--r--kennel/engine/systems/system.py31
13 files changed, 321 insertions, 0 deletions
diff --git a/kennel/engine/__init__.py b/kennel/engine/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/kennel/engine/__init__.py
diff --git a/kennel/engine/components/__init__.py b/kennel/engine/components/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/kennel/engine/components/__init__.py
diff --git a/kennel/engine/components/component.py b/kennel/engine/components/component.py
new file mode 100644
index 0000000..c8ef2d0
--- /dev/null
+++ b/kennel/engine/components/component.py
@@ -0,0 +1,16 @@
+from enum import Enum
+from abc import abstractmethod
+
+
+class ComponentType(str, Enum):
+ POSITION = "POSITION"
+ CONTROLLABLE = "CONTROLLABLE"
+
+
+class Component:
+ def __init__(self, component_type: ComponentType):
+ self.component_type = component_type
+
+ @abstractmethod
+ def dict(self) -> dict:
+ pass
diff --git a/kennel/engine/components/controllable.py b/kennel/engine/components/controllable.py
new file mode 100644
index 0000000..49b0557
--- /dev/null
+++ b/kennel/engine/components/controllable.py
@@ -0,0 +1,13 @@
+from .component import Component, ComponentType
+
+
+class Controllable(Component):
+ def __init__(self, by: str):
+ super().__init__(ComponentType.CONTROLLABLE)
+ self.by = by
+
+ def __repr__(self) -> str:
+ return f"Controllable(by={self.by})"
+
+ def dict(self) -> dict:
+ return {"by": self.by}
diff --git a/kennel/engine/components/position.py b/kennel/engine/components/position.py
new file mode 100644
index 0000000..2a4da4a
--- /dev/null
+++ b/kennel/engine/components/position.py
@@ -0,0 +1,14 @@
+from .component import Component, ComponentType
+
+
+class Position(Component):
+ def __init__(self, x: float, y: float):
+ super().__init__(ComponentType.POSITION)
+ self.x = x
+ self.y = y
+
+ def __repr__(self) -> str:
+ return f"Position(x={self.x}, y={self.y})"
+
+ def dict(self) -> dict:
+ return {"x": self.x, "y": self.y}
diff --git a/kennel/engine/entities/__init__.py b/kennel/engine/entities/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/kennel/engine/entities/__init__.py
diff --git a/kennel/engine/entities/cat.py b/kennel/engine/entities/cat.py
new file mode 100644
index 0000000..02fdb33
--- /dev/null
+++ b/kennel/engine/entities/cat.py
@@ -0,0 +1,33 @@
+#
+# # IDLE, FROLICKING, EEPY, ALERT, CHASING_CURSOR, CHASING_CAT, SCRATCHING, ITCHY
+# state_stochastic_matrix = [
+# # IDLE
+# [0.5, 0.1, 0.1, 0.1, 0.1, 0.05, 0.05, 0],
+# # FROLICKING
+# [0.1, 0.5, 0.1, 0.1, 0.1, 0.05, 0.05, 0],
+# # EEPY
+# [0.1, 0.1, 0.5, 0.1, 0.1, 0.05, 0.05, 0],
+# # ALERT
+# [0.1, 0.1, 0.1, 0.5, 0.1, 0.05, 0.05, 0],
+# # CHASING_CURSOR
+# [0.1, 0.1, 0.1, 0.1, 0.5, 0.05, 0.05, 0],
+# # CHASING_CAT
+# [0.1, 0.1, 0.1, 0.1, 0.1, 0.5, 0.05, 0],
+# # SCRATCHING
+# [0.1, 0.1, 0.1, 0.1, 0.1, 0.05, 0.5, 0],
+# # ITCHY
+# [0, 0, 0, 0, 0, 0, 0, 1],
+# ]
+#
+#
+# class CatState(Enum):
+# IDLE = 0
+# FROLICKING = 1
+# EEPY = 2
+# ALERT = 3
+# CHASING_CURSOR = 4
+# CHASING_CAT = 5
+# SCRATCHING = 6
+# ITCHY = 7
+#
+#
diff --git a/kennel/engine/entities/entity.py b/kennel/engine/entities/entity.py
new file mode 100644
index 0000000..0ca8666
--- /dev/null
+++ b/kennel/engine/entities/entity.py
@@ -0,0 +1,60 @@
+from enum import Enum
+from kennel.engine.components.component import Component, ComponentType
+from typing import List, Optional
+
+
+class EntityType(str, Enum):
+ LASER = "LASER"
+
+
+class Entity:
+ def __init__(
+ self, entity_type: EntityType, id: str, component_list: List[Component]
+ ):
+ self.entity_type = entity_type
+ self.id = id
+ self.components = {}
+ for component in component_list:
+ self.add_component(component)
+
+ def get_component(self, component_type: ComponentType) -> Optional[Component]:
+ return self.components[component_type]
+
+ def add_component(self, component: Component) -> None:
+ self.components[component.component_type] = component
+
+
+class EntityManager:
+ def __init__(self):
+ self.entities = {}
+ self.component_entities = {}
+
+ def update(self) -> None:
+ self.component_entities = {}
+ for entity in self.entities.values():
+ for component in entity.components.values():
+ if component.component_type not in self.component_entities:
+ self.component_entities[component.component_type] = set()
+ self.component_entities[component.component_type].add(entity)
+
+ def get_entities_with_component(
+ self, component_type: ComponentType
+ ) -> List[Entity]:
+ return self.component_entities.get(component_type, [])
+
+ def add_entity(self, entity: Entity) -> None:
+ self.entities[entity.id] = entity
+ for component in entity.components.values():
+ if component.component_type not in self.component_entities:
+ self.component_entities[component.component_type] = set()
+ self.component_entities[component.component_type].add(entity)
+
+ def remove_entity(self, entity_id: str) -> None:
+ if entity_id in self.entities:
+ entity = self.entities[entity_id]
+ for component in entity.components.values():
+ if component.component_type in self.component_entities:
+ self.component_entities[component.component_type].remove(entity)
+
+ def get_entity(self, entity_id: str) -> Optional[Entity]:
+ return self.entities.get(entity_id)
diff --git a/kennel/engine/entities/laser.py b/kennel/engine/entities/laser.py
new file mode 100644
index 0000000..ead72a6
--- /dev/null
+++ b/kennel/engine/entities/laser.py
@@ -0,0 +1,13 @@
+from .entity import Entity, EntityType
+from kennel.engine.components.position import Position
+from kennel.engine.components.controllable import Controllable
+
+
+class Laser(Entity):
+ def __init__(self, id: str, controllable_by: str):
+ components = [
+ Position(0, 0),
+ Controllable(controllable_by),
+ ]
+
+ super().__init__(EntityType.LASER, id, components)
diff --git a/kennel/engine/game.py b/kennel/engine/game.py
new file mode 100644
index 0000000..2236306
--- /dev/null
+++ b/kennel/engine/game.py
@@ -0,0 +1,53 @@
+from kennel.engine.entities.entity import EntityManager
+from kennel.engine.systems.system import SystemManager
+from kennel.app import logger
+from typing import List, Optional
+import time
+import asyncio
+
+
+class Game:
+ def __init__(
+ self,
+ entity_manager: EntityManager,
+ system_manager: SystemManager,
+ min_time_step: float,
+ ):
+ self.entity_manager = entity_manager
+ self.system_manager = system_manager
+ self.min_time_step = min_time_step
+
+ self.last_time = time.time()
+ self.running = False
+
+ async def update(self, delta_time: float) -> None:
+ self.entity_manager.update()
+ await self.system_manager.update(self.entity_manager, delta_time)
+
+ async def run(self) -> None:
+ logger.info("Game started")
+ self.running = True
+ self.last_time = time.time()
+ steps_since_log = 0
+
+ while self.running:
+ current_time = time.time()
+ if (int(current_time) - int(self.last_time)) == 1 and int(
+ current_time
+ ) % 10 == 0:
+ logger.info(f"Game loop: {steps_since_log} steps since last log time")
+ steps_since_log = 0
+
+ delta_time = current_time - self.last_time
+ self.last_time = current_time
+
+ await self.update(delta_time)
+ await asyncio.sleep(
+ max(self.min_time_step - (time.time() - current_time), 0)
+ )
+
+ steps_since_log += 1
+
+ def stop(self) -> None:
+ self.running = False
+ logger.info("Game stopped")
diff --git a/kennel/engine/systems/__init__.py b/kennel/engine/systems/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/kennel/engine/systems/__init__.py
diff --git a/kennel/engine/systems/network.py b/kennel/engine/systems/network.py
new file mode 100644
index 0000000..7df8088
--- /dev/null
+++ b/kennel/engine/systems/network.py
@@ -0,0 +1,88 @@
+from enum import Enum
+from kennel.engine.entities.entity import EntityManager
+from .system import System, SystemType
+from abc import abstractmethod
+import asyncio
+
+
+class EventType(str, Enum):
+ ENTITY_BORN = "ENTITY_BORN"
+ ENTITY_POSITION_UPDATE = "ENTITY_POSITION_UPDATE"
+ ENTITY_DEATH = "ENTITY_DEATH"
+
+
+class Event:
+ def __init__(self, event_type: EventType, data: dict):
+ self.event_type = event_type
+ self.data = data
+
+ def __str__(self):
+ return f"Event({self.event_type}, {self.data})"
+
+ def dict(self):
+ return {"event_type": self.event_type, "data": self.data}
+
+ @staticmethod
+ def from_dict(data: dict):
+ return Event(EventType(data["event_type"]), data["data"])
+
+
+class Publishable:
+ @abstractmethod
+ async def publish(self, event: Event):
+ pass
+
+
+class ClientEventTransformer:
+ @abstractmethod
+ def apply(self, event: Event) -> Event:
+ pass
+
+
+class EventProcessor:
+ @abstractmethod
+ def accept(self, entity_manager: EntityManager, event: Event) -> None:
+ pass
+
+
+class NetworkSystem(System):
+ def __init__(
+ self,
+ event_processor: EventProcessor,
+ client_event_transformer: ClientEventTransformer,
+ ):
+ super().__init__(SystemType.NETWORK)
+ self.event_processor = event_processor
+ self.client_event_transformer = client_event_transformer
+
+ self.events = []
+ self.clients = {}
+
+ async def update(self, entity_manager: EntityManager, delta_time: float) -> None:
+ for event in self.events:
+ self.event_processor.accept(entity_manager, event)
+ client_events = [
+ self.client_event_transformer.apply(entity_manager, event)
+ for event in self.events
+ ]
+ await asyncio.gather(
+ *[
+ client.publish(event)
+ for client in self.clients.values()
+ for event in client_events
+ ]
+ )
+ self.events = []
+
+ def client_event(self, client_id: str, event: Event) -> None:
+ event.data["client_id"] = client_id
+ self.events.append(event)
+
+ def add_event(self, event: Event) -> None:
+ self.events.append(event)
+
+ def add_client(self, client_id: str, client: Publishable) -> None:
+ self.clients[client_id] = client
+
+ def remove_client(self, client_id: str) -> None:
+ del self.clients[client_id]
diff --git a/kennel/engine/systems/system.py b/kennel/engine/systems/system.py
new file mode 100644
index 0000000..0e51f3a
--- /dev/null
+++ b/kennel/engine/systems/system.py
@@ -0,0 +1,31 @@
+from enum import Enum
+from kennel.engine.entities.entity import EntityManager
+from abc import abstractmethod
+
+
+class SystemType(str, Enum):
+ NETWORK = "NETWORK"
+
+
+class System:
+ def __init__(self, system_type: SystemType):
+ self.system_type = system_type
+
+ @abstractmethod
+ async def update(self, entity_manager: EntityManager, delta_time: float):
+ pass
+
+
+class SystemManager:
+ def __init__(self):
+ self.systems = {}
+
+ def add_system(self, system: System) -> None:
+ self.systems[system.system_type] = system
+
+ def get_system(self, system_type: SystemType) -> System:
+ return self.systems.get(system_type)
+
+ async def update(self, entity_manager: EntityManager, delta_time: float) -> None:
+ for system in self.systems.values():
+ await system.update(entity_manager, delta_time)