Compare commits
	
		
			1 Commits
		
	
	
		
			e47d451426
			...
			8ec7f53682
		
	
	| Author | SHA1 | Date | 
|---|---|---|
|  | 8ec7f53682 | 
|  | @ -17,5 +17,7 @@ class Config: | |||
| 
 | ||||
|     COOKIE_MAX_AGE = int(os.getenv("COOKIE_MAX_AGE", 60 * 60 * 24 * 7))  # 1 week | ||||
| 
 | ||||
|     KENNEL_CATS_POLL_SEC = int(os.getenv("KENNEL_CATS_POLL_SEC", 10)) | ||||
| 
 | ||||
| 
 | ||||
| config = Config() | ||||
|  |  | |||
|  | @ -5,6 +5,7 @@ from enum import Enum | |||
| class ComponentType(str, Enum): | ||||
|     POSITION = "POSITION" | ||||
|     CONTROLLABLE = "CONTROLLABLE" | ||||
|     MARKOV = "MARKOV" | ||||
| 
 | ||||
| 
 | ||||
| class Component: | ||||
|  |  | |||
|  | @ -0,0 +1,24 @@ | |||
| from kennel.engine.components.component import Component, ComponentType | ||||
| 
 | ||||
| 
 | ||||
| class MarkovTransitionState(Component): | ||||
|     def __init__( | ||||
|         self, | ||||
|         state_names: dict[int, str], | ||||
|         initial_state_vector: list[float], | ||||
|         transition_matrix: list[list[float]], | ||||
|     ): | ||||
|         # TODO: Poll rate per state? | ||||
|         # TODO: State being an enum instead of a vector, just choose max and map | ||||
|         self.state_names = state_names | ||||
|         self.state = initial_state_vector | ||||
|         self.transition_matrix = transition_matrix | ||||
| 
 | ||||
|         super().__init__(ComponentType.MARKOV) | ||||
| 
 | ||||
|     def get_max_state_name(self, state_vector: list[float]): | ||||
|         max_val = max(state_vector) | ||||
|         return self.state_names[state_vector.index(max_val)] | ||||
| 
 | ||||
|     def to_dict(self): | ||||
|         return {"state": self.get_max_state_name(self.state)} | ||||
|  | @ -1,6 +1,18 @@ | |||
| from kennel.engine.components.position import Position | ||||
| 
 | ||||
| from .entity import Entity, EntityType | ||||
| 
 | ||||
| 
 | ||||
| class Cat(Entity): | ||||
|     def __init__(self, id: str): | ||||
|         components = [Position(0, 0)] | ||||
| 
 | ||||
|         super().__init__(EntityType.CAT, id, components) | ||||
| 
 | ||||
| 
 | ||||
| # | ||||
| # # IDLE, FROLICKING, EEPY, ALERT, CHASING_CURSOR, CHASING_CAT, SCRATCHING, ITCHY | ||||
| # state_stochastic_matrix = [ | ||||
| # state_stochastic_matrix = [ [1, 0] | ||||
| #     # IDLE | ||||
| #     [0.5, 0.1, 0.1, 0.1, 0.1, 0.05, 0.05, 0], | ||||
| #     # FROLICKING | ||||
|  |  | |||
|  | @ -6,6 +6,7 @@ from kennel.engine.components.component import Component, ComponentType | |||
| 
 | ||||
| class EntityType(str, Enum): | ||||
|     LASER = "LASER" | ||||
|     CAT = "CAT" | ||||
| 
 | ||||
| 
 | ||||
| class Entity: | ||||
|  |  | |||
|  | @ -0,0 +1,13 @@ | |||
| from kennel.engine.components.component import ComponentType | ||||
| from kennel.engine.entities.entity import EntityManager | ||||
| from kennel.engine.systems.system import System, SystemType | ||||
| from kennel.engine.systems.network import NetworkSystem | ||||
| 
 | ||||
| 
 | ||||
| class MarkovTransitionStateSystem(System): | ||||
|     def __init__(self, network_system: NetworkSystem): | ||||
|         super().__init__(SystemType.MARKOV) | ||||
| 
 | ||||
|     def update(self, entity_manager: EntityManager, delta_time: float): | ||||
|         entity_manager.get_entities_with_component(ComponentType.MARKOV) | ||||
|         return | ||||
|  | @ -7,6 +7,7 @@ from kennel.engine.entities.entity import EntityManager | |||
| class SystemType(str, Enum): | ||||
|     NETWORK = "NETWORK" | ||||
|     WORLD = "WORLD" | ||||
|     MARKOV = "MARKOV" | ||||
| 
 | ||||
| 
 | ||||
| class System: | ||||
|  | @ -14,7 +15,7 @@ class System: | |||
|         self.system_type = system_type | ||||
| 
 | ||||
|     @abstractmethod | ||||
|     async def update(self, entity_manager: EntityManager, delta_time: float): | ||||
|     async def update(self, entity_manager: EntityManager, delta_time: float) -> None: | ||||
|         pass | ||||
| 
 | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,18 +1,27 @@ | |||
| import asyncio | ||||
| import uuid | ||||
| import time | ||||
| from typing import List, Optional | ||||
| 
 | ||||
| from kennel.config import config | ||||
| from kennel.engine.components.component import ComponentType | ||||
| from kennel.engine.entities.entity import Entity, EntityManager | ||||
| from kennel.engine.entities.laser import Laser | ||||
| from kennel.engine.entities.cat import Cat | ||||
| from kennel.engine.game import Game | ||||
| from kennel.engine.systems.markov_transition_state_system import ( | ||||
|     MarkovTransitionStateSystem, | ||||
| ) | ||||
| from kennel.engine.systems.network import ( | ||||
|     EntityPositionUpdateEvent, | ||||
|     EntityBornEvent, | ||||
|     EntityDeathEvent, | ||||
|     Event, | ||||
|     EventType, | ||||
|     NetworkSystem, | ||||
|     UpstreamEventProcessor, | ||||
| ) | ||||
| from kennel.kennelcats import KennelCatService, KennelCat | ||||
| from kennel.engine.systems.system import SystemManager | ||||
| from kennel.engine.systems.world import WorldSystem | ||||
| 
 | ||||
|  | @ -58,12 +67,76 @@ class KennelEventProcessor(UpstreamEventProcessor): | |||
|         return event | ||||
| 
 | ||||
| 
 | ||||
| system_manager.add_system(WorldSystem(config.WORLD_WIDTH, config.WORLD_HEIGHT)) | ||||
| system_manager.add_system( | ||||
|     NetworkSystem(KennelEventProcessor()), | ||||
| ) | ||||
| class KennelCatsManager: | ||||
|     kennel_cat_service: KennelCatService | ||||
|     entity_manager: EntityManager | ||||
|     network_system: NetworkSystem | ||||
|     last_seen: set[str] | ||||
|     poll_interval_sec: int | ||||
|     running: bool | ||||
| 
 | ||||
|     def __init__( | ||||
|         self, | ||||
|         kennel_cat_service: KennelCatService, | ||||
|         entity_manager: EntityManager, | ||||
|         network_system: NetworkSystem, | ||||
|         poll_interval_sec: int, | ||||
|     ): | ||||
|         self.kennel_cat_service = kennel_cat_service | ||||
|         self.entity_manager = entity_manager | ||||
|         self.network_system = network_system | ||||
|         self.poll_interval_sec = poll_interval_sec | ||||
| 
 | ||||
|         self.last_seen = set() | ||||
|         self.running = False | ||||
| 
 | ||||
|     async def start(self) -> None: | ||||
|         logger.info("starting kennel cats manager") | ||||
|         if self.running: | ||||
|             return | ||||
| 
 | ||||
|         self.running = True | ||||
|         while self.running: | ||||
|             logger.info("polling kennel cats service") | ||||
|             cats = self.kennel_cat_service.get_kennel() | ||||
|             cats_table = {cat.id: cat for cat in cats} | ||||
|             cat_ids = set([cat.id for cat in cats]) | ||||
| 
 | ||||
|             removed_cats = [cats_table[id] for id in self.last_seen.difference(cat_ids)] | ||||
|             added_cats = [cats_table[id] for id in cat_ids.difference(self.last_seen)] | ||||
|             logger.info(f"removing {removed_cats}") | ||||
|             logger.info(f"adding {added_cats}") | ||||
| 
 | ||||
|             for removed in removed_cats: | ||||
|                 self.entity_manager.remove_entity(removed) | ||||
|                 entity_death = EntityDeathEvent(removed.id) | ||||
|                 self.network_system.server_global_event(entity_death) | ||||
| 
 | ||||
|             for added in added_cats: | ||||
|                 new_cat = Cat(added.id) | ||||
|                 self.entity_manager.add_entity(new_cat) | ||||
|                 entity_born = EntityBornEvent(new_cat) | ||||
|                 self.network_system.server_global_event(entity_born) | ||||
| 
 | ||||
|             self.last_seen = cat_ids | ||||
|             await asyncio.sleep(self.poll_interval_sec) | ||||
| 
 | ||||
|     def stop(self) -> None: | ||||
|         logger.info("stopping kennel cats manager") | ||||
|         self.running = False | ||||
| 
 | ||||
| 
 | ||||
| network_system = NetworkSystem(KennelEventProcessor()) | ||||
| world_system = WorldSystem(config.WORLD_WIDTH, config.WORLD_HEIGHT) | ||||
| markov_transition_state_system = MarkovTransitionStateSystem(network_system) | ||||
| system_manager.add_system(network_system) | ||||
| system_manager.add_system(world_system) | ||||
| 
 | ||||
| kennel = Game(entity_manager, system_manager, config.MIN_TIME_STEP) | ||||
| kennel_cat_service = KennelCatService(config.HATECOMPUTERS_ENDPOINT) | ||||
| kennel_cats_manager = KennelCatsManager( | ||||
|     kennel_cat_service, entity_manager, network_system, config.KENNEL_CATS_POLL_SEC | ||||
| ) | ||||
| 
 | ||||
| 
 | ||||
| def create_session_controllable_entities(session: str) -> List[Entity]: | ||||
|  |  | |||
|  | @ -37,10 +37,10 @@ class KennelCat: | |||
| 
 | ||||
| 
 | ||||
| class KennelCatService: | ||||
|     def __init__(self, endpoint: str): | ||||
|         self.endpoint = endpoint | ||||
|     def __init__(self, hc_endpoint: str): | ||||
|         self.hc_endpoint = hc_endpoint | ||||
| 
 | ||||
|     def get_kennel(self) -> List[KennelCat]: | ||||
|         response = requests.get(f"{self.endpoint}/kennel") | ||||
|         response = requests.get(f"{self.hc_endpoint}/kennel") | ||||
|         response.raise_for_status() | ||||
|         return [KennelCat.from_dict(cat) for cat in response.json()] | ||||
|  |  | |||
|  | @ -30,6 +30,7 @@ from kennel.kennel import ( | |||
|     create_session_controllable_entities, | ||||
|     entity_manager, | ||||
|     kennel, | ||||
|     kennel_cats_manager, | ||||
|     system_manager, | ||||
| ) | ||||
| 
 | ||||
|  | @ -43,12 +44,14 @@ loop = asyncio.get_event_loop() | |||
| async def startup_event(): | ||||
|     logger.info("Starting Kennel...") | ||||
|     loop.create_task(kennel.run()) | ||||
|     loop.create_task(kennel_cats_manager.start()) | ||||
| 
 | ||||
| 
 | ||||
| @app.on_event("shutdown") | ||||
| async def shutdown_event(): | ||||
|     logger.info("Stopping Kennel...") | ||||
|     kennel.stop() | ||||
|     kennel_cats_manager.stop() | ||||
|     loop.stop() | ||||
|     logger.info("Kennel stopped") | ||||
| 
 | ||||
|  |  | |||
|  | @ -4,10 +4,9 @@ | |||
|   "version": "0.1.0", | ||||
|   "type": "module", | ||||
|   "scripts": { | ||||
|     "dev": "vite", | ||||
|     "build": "tsc && vite build", | ||||
|     "preview": "vite preview", | ||||
|     "watch": "./node_modules/nodemon/bin/nodemon.js --watch './src/**/*' -e ts,html --exec \"npm run build\"" | ||||
|     "dev": "./node_modules/nodemon/bin/nodemon.js --watch './src/**/*' -e ts,html --exec \"npm run build\"" | ||||
|   }, | ||||
|   "devDependencies": { | ||||
|     "@rollup/plugin-inject": "^5.0.5", | ||||
|  |  | |||
|  | @ -1,12 +1,14 @@ | |||
| import { | ||||
|   Component, | ||||
|   ComponentType, | ||||
|   PositionComponent, | ||||
|   RenderableComponent, | ||||
|   TrailingPositionComponent, | ||||
| } from "./component"; | ||||
| 
 | ||||
| export enum EntityType { | ||||
|   LASER = "LASER", | ||||
|   CAT = "CAT", | ||||
| } | ||||
| 
 | ||||
| export interface Entity { | ||||
|  | @ -28,3 +30,16 @@ export const create_laser = (base: Entity) => { | |||
|   base.components[ComponentType.RENDERABLE] = renderable; | ||||
|   return base; | ||||
| }; | ||||
| 
 | ||||
| export const create_cat = (base: Entity) => { | ||||
|   const renderable: RenderableComponent = { | ||||
|     name: ComponentType.RENDERABLE, | ||||
|   }; | ||||
|   base.components[ComponentType.RENDERABLE] = renderable; | ||||
|   base.components[ComponentType.POSITION] = { | ||||
|     component: ComponentType.POSITION, | ||||
|     x: Math.random() * 1_000, | ||||
|     y: Math.random() * 1_000, | ||||
|   } as unknown as PositionComponent; // TODO: hack
 | ||||
|   return base; | ||||
| }; | ||||
|  |  | |||
|  | @ -3,7 +3,7 @@ import { | |||
|   PositionComponent, | ||||
|   TrailingPositionComponent, | ||||
| } from "./component"; | ||||
| import { create_laser, Entity, EntityType } from "./entity"; | ||||
| import { create_cat, create_laser, Entity, EntityType } from "./entity"; | ||||
| import { | ||||
|   EntityBornEvent, | ||||
|   EntityDeathEvent, | ||||
|  | @ -110,6 +110,9 @@ export class NetworkSystem extends System { | |||
|     if (entity.entity_type === EntityType.LASER) { | ||||
|       return create_laser(entity); | ||||
|     } | ||||
|     if (entity.entity_type === EntityType.CAT) { | ||||
|       return create_cat(entity); | ||||
|     } | ||||
|     return entity; | ||||
|   } | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,4 +1,8 @@ | |||
| import { ComponentType, TrailingPositionComponent } from "./component"; | ||||
| import { | ||||
|   ComponentType, | ||||
|   PositionComponent, | ||||
|   TrailingPositionComponent, | ||||
| } from "./component"; | ||||
| import { Game } from "./game"; | ||||
| import { System, SystemType } from "./system"; | ||||
| import { drawLaserPen } from "laser-pen"; | ||||
|  | @ -25,6 +29,17 @@ export class RenderSystem extends System { | |||
|         ] as TrailingPositionComponent; | ||||
|         if (trailing_position.trails.length < 3) return; | ||||
|         drawLaserPen(ctx, trailing_position.trails); | ||||
|         return; | ||||
|       } | ||||
| 
 | ||||
|       if (ComponentType.POSITION in entity.components) { | ||||
|         const position = entity.components[ | ||||
|           ComponentType.POSITION | ||||
|         ] as PositionComponent; | ||||
|         ctx.beginPath(); | ||||
|         ctx.arc(position.x, position.y, 50, 0, 2 * Math.PI); | ||||
|         ctx.stroke(); | ||||
|         return; | ||||
|       } | ||||
|     }); | ||||
|   } | ||||
|  |  | |||
|  | @ -28,11 +28,11 @@ $(async () => { | |||
|   const publisher: EventPublisher = new WebsocketEventPublisher(ws); | ||||
|   const network_system = new NetworkSystem(queue, publisher); | ||||
| 
 | ||||
|   const gamecanvas = $("#gamecanvas").get(0)!; | ||||
|   const gamecanvas = $("#gamecanvas").get(0)! as HTMLCanvasElement; | ||||
|   const input_system = new InputSystem(publisher, gamecanvas); | ||||
| 
 | ||||
|   setDelay(1_000); | ||||
|   const render_system = new RenderSystem(gamecanvas as HTMLCanvasElement); | ||||
|   setDelay(500); | ||||
|   const render_system = new RenderSystem(gamecanvas); | ||||
| 
 | ||||
|   const trailing_position = new TrailingPositionSystem(drainPoints); | ||||
| 
 | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue