From deea62212cc083168e9648fda0ae1e5b4e072181 Mon Sep 17 00:00:00 2001 From: Kanewersa <30356293+Kanewersa@users.noreply.github.com> Date: Sun, 6 Jun 2021 19:55:55 +0200 Subject: [PATCH] Add reinforcement learning --- survival/__init__.py | 58 ++++----- survival/components/consumption_component.py | 5 + survival/components/inventory_component.py | 9 ++ survival/components/learning_component.py | 32 +++++ survival/components/pathfinding_component.py | 3 +- survival/components/time_component.py | 14 +- survival/components/vision_component.py | 34 +++++ survival/entity_layer.py | 2 +- survival/esper.py | 11 ++ survival/game_map.py | 52 +++++++- survival/generators/player_generator.py | 22 +++- survival/generators/resource_generator.py | 29 +++-- survival/generators/world_generator.py | 109 +++++++++++++--- survival/graph_search.py | 42 +++++- survival/image.py | 7 +- survival/learning_utils.py | 112 ++++++++++++++++ survival/model.py | 78 ++++++++++++ survival/model/model.pth | Bin 0 -> 17167 bytes survival/model/model204games.pth | Bin 0 -> 17167 bytes survival/model/modeltrained.pth | Bin 0 -> 17167 bytes survival/model/modeltrained2.pth | Bin 0 -> 17167 bytes survival/model/new_model120games.pth | Bin 0 -> 17167 bytes survival/model/newer_model120games.pth | Bin 0 -> 17167 bytes survival/settings.py | 7 +- survival/systems/automation_system.py | 2 + survival/systems/collection_system.py | 20 --- survival/systems/collision_system.py | 2 +- survival/systems/consumption_system.py | 30 +++++ survival/systems/draw_system.py | 4 +- survival/systems/input_system.py | 2 +- survival/systems/movement_system.py | 18 +-- survival/systems/neural_system.py | 120 ++++++++++++++++++ .../systems/pathfinding_movement_system.py | 12 +- survival/systems/vision_system.py | 18 +++ survival/user_interface.py | 7 +- 35 files changed, 739 insertions(+), 122 deletions(-) create mode 100644 survival/components/consumption_component.py create mode 100644 survival/components/learning_component.py create mode 100644 survival/components/vision_component.py create mode 100644 survival/learning_utils.py create mode 100644 survival/model.py create mode 100644 survival/model/model.pth create mode 100644 survival/model/model204games.pth create mode 100644 survival/model/modeltrained.pth create mode 100644 survival/model/modeltrained2.pth create mode 100644 survival/model/new_model120games.pth create mode 100644 survival/model/newer_model120games.pth delete mode 100644 survival/systems/collection_system.py create mode 100644 survival/systems/consumption_system.py create mode 100644 survival/systems/neural_system.py create mode 100644 survival/systems/vision_system.py diff --git a/survival/__init__.py b/survival/__init__.py index dc3e76a..8fd5730 100644 --- a/survival/__init__.py +++ b/survival/__init__.py @@ -1,5 +1,3 @@ -import random - import pygame from settings import SCREEN_WIDTH, SCREEN_HEIGHT @@ -12,6 +10,31 @@ from survival.generators.resource_generator import ResourceGenerator from survival.generators.world_generator import WorldGenerator from survival.systems.draw_system import DrawSystem + +class Game: + def __init__(self): + self.world_generator = WorldGenerator(win, self.reset) + self.game_map, self.world, self.camera = self.world_generator.create_world() + self.run = True + + def reset(self): + self.world_generator.reset_world() + + def update(self, ms): + events = pygame.event.get() + + for event in events: + if event.type == pygame.QUIT: + self.run = False + if pygame.key.get_pressed()[pygame.K_DELETE]: + self.reset() + + win.fill((0, 0, 0)) + self.game_map.draw(self.camera) + self.world.process(ms) + pygame.display.update() + + if __name__ == '__main__': pygame.init() @@ -21,32 +44,7 @@ if __name__ == '__main__': pygame.display.set_caption("AI Project") clock = pygame.time.Clock() + game = Game() - game_map = GameMap(int(SCREEN_WIDTH / 32) * 2, 2 * int(SCREEN_HEIGHT / 32) + 1) - camera = Camera(game_map.width * 32, game_map.height * 32, win) - - world = WorldGenerator().create_world(camera, game_map) - player = PlayerGenerator().create_player(world, game_map) - world.get_processor(DrawSystem).initialize_interface(world.component_for_entity(player, InventoryComponent)) - building = BuildingGenerator().create_home(world, game_map) - - ResourceGenerator(world, game_map).generate_resources(player) - - run = True - - while run: - # Set the framerate - ms = clock.tick(60) - - events = pygame.event.get() - - for event in events: - if event.type == pygame.QUIT: - run = False - - keys = pygame.key.get_pressed() - - win.fill((0, 0, 0)) - game_map.draw(camera) - world.process(ms) - pygame.display.update() + while game.run: + game.update(clock.tick(60)) diff --git a/survival/components/consumption_component.py b/survival/components/consumption_component.py new file mode 100644 index 0000000..7294a29 --- /dev/null +++ b/survival/components/consumption_component.py @@ -0,0 +1,5 @@ +class ConsumptionComponent: + def __init__(self, inventory_state=0): + self.timer_value: float = 2000 + self.timer: float = self.timer_value + self.last_inventory_state = inventory_state diff --git a/survival/components/inventory_component.py b/survival/components/inventory_component.py index 9452dc4..72b2f73 100644 --- a/survival/components/inventory_component.py +++ b/survival/components/inventory_component.py @@ -19,3 +19,12 @@ class InventoryComponent: def has_item(self, item): return item in self.items and self.items[item] != 0 + + def total_items_count(self): + total = 0 + for item, value in self.items.items(): + total += value + return total + + def clear(self): + self.items = {} diff --git a/survival/components/learning_component.py b/survival/components/learning_component.py new file mode 100644 index 0000000..b2be0a1 --- /dev/null +++ b/survival/components/learning_component.py @@ -0,0 +1,32 @@ +from survival.components.time_component import TimeComponent + + +class LearningComponent: + def __init__(self): + self.made_step = False + self.old_state = None + self.action = None + self.resource = None + + self.reward = 0 + self.done = False + self.score = 0 + self.record = 0 + + def load_step(self, old_state, action, resource): + self.old_state = old_state + self.action = action + if resource is None: + self.resource = None + else: + self.resource = resource + self.made_step = True + + def reset(self): + self.made_step = False + self.old_state = None + self.action = None + self.resource = None + + self.reward = 0 + self.done = False diff --git a/survival/components/pathfinding_component.py b/survival/components/pathfinding_component.py index ca61af6..4dbe908 100644 --- a/survival/components/pathfinding_component.py +++ b/survival/components/pathfinding_component.py @@ -1,6 +1,5 @@ class PathfindingComponent: - def __init__(self, target_pos, searching_for_resource=False): + def __init__(self, target_pos): self.target_grid_pos = (int(target_pos[0] / 32), int(target_pos[1] / 32)) - self.searching_for_resource = False self.current_target = None self.path = None diff --git a/survival/components/time_component.py b/survival/components/time_component.py index e8bd962..52ec18a 100644 --- a/survival/components/time_component.py +++ b/survival/components/time_component.py @@ -1,5 +1,5 @@ class TimeComponent: - def __init__(self, minute, hour, day, timer): + def __init__(self, minute=0, hour=0, day=0, timer=0): self.minute = minute self.hour = hour self.day = day @@ -16,5 +16,17 @@ class TimeComponent: self.hour = temp2 self.minute = temp + def total_minutes(self): + return self.minute + self.hour * 60 + self.day * 1440 + def __str__(self): return f'Day {self.day}, {self.hour}:{self.minute}' + + def __eq__(self, other): + return self.total_minutes() == other.total_minutes() + + def __gt__(self, other): + return self.total_minutes() > other.total_minutes() + + def __lt__(self, other): + return self.total_minutes() < other.total_minutes() diff --git a/survival/components/vision_component.py b/survival/components/vision_component.py new file mode 100644 index 0000000..f72bafc --- /dev/null +++ b/survival/components/vision_component.py @@ -0,0 +1,34 @@ +from pygame import Surface + +from survival.settings import AGENT_VISION_RANGE, SCREEN_WIDTH, SCREEN_HEIGHT + + +class VisionComponent: + def __init__(self): + self.agent_vision = AGENT_VISION_RANGE * 32 * 2 + self.width = SCREEN_WIDTH * 2 + self.height = SCREEN_HEIGHT * 2 + self.surface_l = Surface(((self.width - self.agent_vision) / 2, self.height)) + self.surface_r = Surface(((self.width - self.agent_vision) / 2, self.height)) + self.surface_t = Surface((self.agent_vision, (self.height - self.agent_vision) / 2)) + self.surface_b = Surface((self.agent_vision, (self.height - self.agent_vision) / 2)) + self.surface_l.fill((0, 0, 0)) + self.surface_l.set_alpha(200) + self.surface_r.fill((0, 0, 0)) + self.surface_r.set_alpha(200) + self.surface_t.fill((0, 0, 0)) + self.surface_t.set_alpha(200) + self.surface_b.fill((0, 0, 0)) + self.surface_b.set_alpha(200) + self.l_pos = (0, 0) + self.r_pos = (0, 0) + self.t_pos = (0, 0) + self.b_pos = (0, 0) + + def update_positions(self, position: [int, int]): + new_position = (position[0] - self.width / 2 + 16, position[1] - self.height / 2 + 16) + self.l_pos = new_position + self.r_pos = (new_position[0] + (self.width + self.agent_vision) / 2, new_position[1]) + self.t_pos = (new_position[0] + (self.width - self.agent_vision) / 2, new_position[1]) + self.b_pos = (new_position[0] + (self.width - self.agent_vision) / 2, + new_position[1] + (self.height + self.agent_vision) / 2) diff --git a/survival/entity_layer.py b/survival/entity_layer.py index cd7a9cd..e6f2600 100644 --- a/survival/entity_layer.py +++ b/survival/entity_layer.py @@ -18,7 +18,7 @@ class EntityLayer: def remove_entity(self, pos): self.tiles[pos[1]][pos[0]] = None - def get_entity(self, pos) -> int: + def get_entity(self, pos): return self.tiles[pos[1]][pos[0]] def is_colliding(self, pos): diff --git a/survival/esper.py b/survival/esper.py index ed92a64..8c98ea0 100644 --- a/survival/esper.py +++ b/survival/esper.py @@ -28,6 +28,9 @@ class Processor: def process(self, *args, **kwargs): raise NotImplementedError + def reset(self, *args, **kwargs): + pass + class World: """A World object keeps track of all Entities, Components, and Processors. @@ -46,6 +49,14 @@ class World: self.process_times = {} self._process = self._timed_process + @property + def processors(self): + return self._processors + + @property + def entities(self): + return self._entities + def clear_cache(self) -> None: self.get_component.cache_clear() self.get_components.cache_clear() diff --git a/survival/game_map.py b/survival/game_map.py index 36c04af..60c6251 100644 --- a/survival/game_map.py +++ b/survival/game_map.py @@ -1,4 +1,9 @@ +from survival.components.position_component import PositionComponent +from survival.components.resource_component import ResourceComponent from survival.entity_layer import EntityLayer +from survival.esper import World +from survival.graph_search import graph_search +from survival.settings import AGENT_VISION_RANGE from survival.tile_layer import TileLayer @@ -23,10 +28,55 @@ class GameMap: self.entity_layer.remove_entity(pos) def get_entity(self, pos) -> int: + if not self.in_bounds(pos): + return None return self.entity_layer.get_entity(pos) def is_colliding(self, pos): - return pos[0] < 0 or pos[0] >= self.width or pos[1] < 0 or pos[1] >= self.height or self.entity_layer.is_colliding(pos) + return not self.in_bounds(pos) or self.entity_layer.is_colliding(pos) + + def in_bounds(self, pos): + return 0 <= pos[0] < self.width and 0 <= pos[1] < self.height def get_cost(self, pos): return self.tile_layer.get_cost(pos) + + def find_nearby_resources(self, world: World, player: int, position: PositionComponent, search_range: int = 5): + entity_position = position.grid_position + + x_range = [entity_position[0] - search_range, entity_position[0] + search_range] + y_range = [entity_position[1] - search_range, entity_position[1] + search_range] + + # Check if range is not out of map bounds + if x_range[0] < 0: + x_range[0] = 0 + if x_range[1] >= self.width: + x_range[1] = self.width - 1 + if y_range[0] < 0: + y_range[0] = 0 + if y_range[1] >= self.height: + y_range[1] = self.height - 1 + + found_resources = [] + + for y in range(y_range[0], y_range[1]): + for x in range(x_range[0], x_range[1]): + ent = self.get_entity([x, y]) + if ent == player: + continue + if ent is not None and world.has_component(ent, ResourceComponent): + res_position = world.component_for_entity(ent, PositionComponent).grid_position + path, cost = graph_search(self, position, tuple(res_position), world) + found_resources.append([ent, path, cost]) + + return found_resources + + def find_nearest_resource(self, world: World, player: int, position: PositionComponent): + resources = self.find_nearby_resources(world, player, position, AGENT_VISION_RANGE) + + nearest = None + for resource in resources: + if nearest is None or resource[2] < nearest[2]: + nearest = resource + + return nearest diff --git a/survival/generators/player_generator.py b/survival/generators/player_generator.py index e5febc7..246d4ec 100644 --- a/survival/generators/player_generator.py +++ b/survival/generators/player_generator.py @@ -1,31 +1,41 @@ from survival.components.OnCollisionComponent import OnCollisionComponent from survival.components.camera_target_component import CameraTargetComponent +from survival.components.consumption_component import ConsumptionComponent from survival.components.input_component import InputComponent from survival.components.inventory_component import InventoryComponent +from survival.components.learning_component import LearningComponent from survival.components.movement_component import MovementComponent from survival.components.position_component import PositionComponent from survival.components.sprite_component import SpriteComponent from survival.components.time_component import TimeComponent -from survival.systems.automation_system import AutomationComponent +from survival.components.vision_component import VisionComponent +from survival.generators.resource_type import ResourceType +from survival.settings import PLAYER_START_POSITION, STARTING_RESOURCES_AMOUNT class PlayerGenerator: - def create_player(self, world, game_map): player = world.create_entity() - pos = PositionComponent([0, 0], [0, 0]) + pos = PositionComponent([PLAYER_START_POSITION[0] * 32, PLAYER_START_POSITION[1] * 32], + PLAYER_START_POSITION) world.add_component(player, pos) world.add_component(player, MovementComponent()) world.add_component(player, InputComponent()) world.add_component(player, OnCollisionComponent()) - world.add_component(player, InventoryComponent()) + inv = InventoryComponent() + for resource in ResourceType: + inv.add_item(resource, STARTING_RESOURCES_AMOUNT) + world.add_component(player, ConsumptionComponent(inv.total_items_count())) + world.add_component(player, inv) camera_target = CameraTargetComponent(pos) world.add_component(player, camera_target) - world.add_component(player, AutomationComponent()) + # world.add_component(player, AutomationComponent()) game_map.add_entity(player, pos) sprite = SpriteComponent('stevenson.png') sprite.set_scale(1) world.add_component(player, sprite) - world.add_component(player, TimeComponent(0, 0, 0, 0)) + world.add_component(player, TimeComponent()) + world.add_component(player, VisionComponent()) + world.add_component(player, LearningComponent()) return player diff --git a/survival/generators/resource_generator.py b/survival/generators/resource_generator.py index 289be88..bab5e4e 100644 --- a/survival/generators/resource_generator.py +++ b/survival/generators/resource_generator.py @@ -3,23 +3,24 @@ import random from survival import GameMap from survival.components.OnCollisionComponent import OnCollisionComponent from survival.components.inventory_component import InventoryComponent +from survival.components.learning_component import LearningComponent from survival.components.position_component import PositionComponent from survival.components.resource_component import ResourceComponent from survival.components.sprite_component import SpriteComponent -from survival.decision_tree import DecisionTree from survival.esper import World from survival.generators.resource_type import ResourceType -from survival.settings import RESOURCES_AMOUNT +from survival.settings import RESOURCES_AMOUNT, PLAYER_START_POSITION class ResourceGenerator: + resources_amount = 0 + def __init__(self, world, game_map): self.world = world self.map = game_map - self.decision_tree = DecisionTree() - self.built_tree = self.decision_tree.build(10) def generate_resources(self, player: int): + ResourceGenerator.resources_amount = RESOURCES_AMOUNT for x in range(RESOURCES_AMOUNT): obj = self.world.create_entity() sprites = { @@ -34,7 +35,7 @@ class ResourceGenerator: resource_type = random.choice(list(ResourceType)) sprite = SpriteComponent(sprites[resource_type]) col = OnCollisionComponent() - col.addCallback(self.remove_resource, world=self.world, game_map=self.map, resource_ent=obj, player=player, decision_tree=self.decision_tree) + col.addCallback(self.remove_resource, world=self.world, game_map=self.map, resource_ent=obj, player=player) self.world.add_component(obj, pos) self.world.add_component(obj, sprite) self.world.add_component(obj, col) @@ -43,17 +44,25 @@ class ResourceGenerator: def get_empty_grid_position(self): free_pos = [random.randrange(self.map.width), random.randrange(self.map.height)] - while self.map.is_colliding(free_pos): + while self.map.is_colliding(free_pos) or ( + free_pos[0] == PLAYER_START_POSITION[0] and free_pos[1] == PLAYER_START_POSITION[1]): free_pos = [random.randrange(self.map.width), random.randrange(self.map.height)] return free_pos @staticmethod - def remove_resource(world: World, game_map: GameMap, resource_ent: int, player: int, decision_tree: DecisionTree): + def remove_resource(world: World, game_map: GameMap, resource_ent: int, player: int): pos = world.component_for_entity(resource_ent, PositionComponent) resource = world.component_for_entity(resource_ent, ResourceComponent) inventory = world.component_for_entity(player, InventoryComponent) - answer = decision_tree.predict_answer(resource) - # print(answer) - inventory.add_item(ResourceType.get_from_string(answer), 1) + inventory.add_item(resource.resource_type, 1) game_map.remove_entity(pos.grid_position) world.delete_entity(resource_ent, immediate=True) + if world.has_component(player, LearningComponent): + learning = world.component_for_entity(player, LearningComponent) + learning.reward = 10 + learning.score += 1 + ResourceGenerator.resources_amount -= 1 + if ResourceGenerator.resources_amount == 0: + learning.reward += 50 + learning.done = True + diff --git a/survival/generators/world_generator.py b/survival/generators/world_generator.py index 4005934..c30829e 100644 --- a/survival/generators/world_generator.py +++ b/survival/generators/world_generator.py @@ -1,29 +1,106 @@ -from survival import esper +from survival import esper, PlayerGenerator, ResourceGenerator, SCREEN_WIDTH, SCREEN_HEIGHT, GameMap, \ + Camera +from survival.components.consumption_component import ConsumptionComponent +from survival.components.direction_component import DirectionChangeComponent +from survival.components.inventory_component import InventoryComponent +from survival.components.learning_component import LearningComponent +from survival.components.moving_component import MovingComponent +from survival.components.pathfinding_component import PathfindingComponent +from survival.components.position_component import PositionComponent +from survival.components.resource_component import ResourceComponent +from survival.components.time_component import TimeComponent +from survival.esper import World +from survival.generators.resource_type import ResourceType +from survival.settings import PLAYER_START_POSITION, STARTING_RESOURCES_AMOUNT from survival.systems.automation_system import AutomationSystem from survival.systems.camera_system import CameraSystem -from survival.systems.collection_system import ResourceCollectionSystem from survival.systems.collision_system import CollisionSystem +from survival.systems.consumption_system import ConsumptionSystem from survival.systems.direction_system import DirectionSystem from survival.systems.draw_system import DrawSystem from survival.systems.input_system import InputSystem from survival.systems.movement_system import MovementSystem -from survival.systems.pathfinding_movement_system import PathfindingMovementSystem +from survival.systems.neural_system import NeuralSystem from survival.systems.time_system import TimeSystem +from survival.systems.vision_system import VisionSystem class WorldGenerator: + def __init__(self, win, callback): + self.win = win + self.callback = callback + self.world: World = esper.World() + self.game_map: GameMap = GameMap(int(SCREEN_WIDTH / 32) * 2, 2 * int(SCREEN_HEIGHT / 32) + 1) + self.camera = Camera(self.game_map.width * 32, self.game_map.height * 32, self.win) + self.resource_generator: ResourceGenerator = ResourceGenerator(self.world, self.game_map) + self.player: int = -1 - def create_world(self, camera, game_map): - world = esper.World() - world.add_processor(InputSystem(camera, game_map)) - world.add_processor(CameraSystem(camera)) - world.add_processor(MovementSystem(game_map), priority=2) - world.add_processor(CollisionSystem(game_map), priority=3) - world.add_processor(DrawSystem(camera)) - world.add_processor(ResourceCollectionSystem(), priority=1) - world.add_processor(TimeSystem()) - world.add_processor(AutomationSystem(game_map)) - world.add_processor(PathfindingMovementSystem(game_map), priority=4) - world.add_processor(DirectionSystem()) + def create_world(self): + self.world.add_processor(InputSystem(self.camera, self.game_map)) + self.world.add_processor(CameraSystem(self.camera)) + self.world.add_processor(MovementSystem(self.game_map), priority=20) + self.world.add_processor(CollisionSystem(self.game_map), priority=30) + self.world.add_processor(NeuralSystem(self.game_map, self.callback), priority=50) + self.world.add_processor(DrawSystem(self.camera)) + self.world.add_processor(TimeSystem()) + self.world.add_processor(AutomationSystem(self.game_map)) + # self.world.add_processor(PathfindingMovementSystem(self.game_map), priority=40) + self.world.add_processor(DirectionSystem()) + self.world.add_processor(ConsumptionSystem(self.callback)) + self.world.add_processor(VisionSystem(self.camera)) - return world + self.player = PlayerGenerator().create_player(self.world, self.game_map) + self.world.get_processor(DrawSystem).initialize_interface( + self.world.component_for_entity(self.player, InventoryComponent)) + + # BuildingGenerator().create_home(self.world, self.game_map) + self.resource_generator.generate_resources(self.player) + return self.game_map, self.world, self.camera + + def reset_world(self): + for processor in self.world.processors: + processor.reset() + + self.reset_player() + self.reset_resources() + + def reset_resources(self): + for entity in self.world.entities: + if self.world.has_component(entity, ResourceComponent): + self.game_map.remove_entity(self.world.component_for_entity(entity, PositionComponent).grid_position) + self.world.delete_entity(entity) + continue + self.resource_generator.generate_resources(self.player) + + def reset_player(self): + self.world.remove_component(self.player, TimeComponent) + self.world.add_component(self.player, TimeComponent()) + + inv = self.world.component_for_entity(self.player, InventoryComponent) + inv.clear() + for resource in ResourceType: + inv.add_item(resource, STARTING_RESOURCES_AMOUNT) + + if self.world.has_component(self.player, ConsumptionComponent): + self.world.remove_component(self.player, ConsumptionComponent) + self.world.add_component(self.player, ConsumptionComponent(inv.total_items_count())) + + pos = self.world.component_for_entity(self.player, PositionComponent) + old_pos = pos.grid_position + + self.world.remove_component(self.player, PositionComponent) + self.world.add_component(self.player, + PositionComponent([PLAYER_START_POSITION[0] * 32, PLAYER_START_POSITION[1] * 32], + PLAYER_START_POSITION)) + + self.game_map.move_entity(old_pos, pos.grid_position) + + if self.world.has_component(self.player, MovingComponent): + self.world.remove_component(self.player, MovingComponent) + if self.world.has_component(self.player, DirectionChangeComponent): + self.world.remove_component(self.player, DirectionChangeComponent) + if self.world.has_component(self.player, PathfindingComponent): + self.world.remove_component(self.player, PathfindingComponent) + if self.world.has_component(self.player, LearningComponent): + learning = self.world.component_for_entity(self.player, LearningComponent) + learning.reset() diff --git a/survival/graph_search.py b/survival/graph_search.py index 1f3495a..1691a5b 100644 --- a/survival/graph_search.py +++ b/survival/graph_search.py @@ -2,7 +2,8 @@ from enum import Enum from queue import PriorityQueue from typing import Tuple, List -from survival import GameMap +from survival.components.direction_component import DirectionChangeComponent +from survival.components.moving_component import MovingComponent from survival.components.position_component import PositionComponent from survival.components.resource_component import ResourceComponent from survival.enums import Direction @@ -14,6 +15,33 @@ class Action(Enum): ROTATE_RIGHT = 1 MOVE = 2 + @staticmethod + def from_array(action): + if action[0] == 1: + return Action.MOVE + if action[1] == 1: + return Action.ROTATE_LEFT + if action[2] == 1: + return Action.ROTATE_RIGHT + raise Exception("Unknown action.") + + @staticmethod + def perform(world, entity, action): + if world.has_component(entity, MovingComponent): + raise Exception(f"Entity was already moving. Could not perform action: {action}") + if world.has_component(entity, DirectionChangeComponent): + raise Exception(f"Entity was already rotating. Could not perform action: {action}") + + if action == Action.ROTATE_LEFT: + world.add_component(entity, DirectionChangeComponent( + Direction.rotate_left(world.component_for_entity(entity, PositionComponent).direction))) + elif action == Action.ROTATE_RIGHT: + world.add_component(entity, DirectionChangeComponent( + Direction.rotate_right(world.component_for_entity(entity, PositionComponent).direction))) + else: + world.add_component(entity, MovingComponent()) + return action + class State: def __init__(self, position: Tuple[int, int], direction: Direction): @@ -40,7 +68,7 @@ def get_moved_position(position: Tuple[int, int], direction: Direction): return position[0] + vector[0], position[1] + vector[1] -def get_states(state: State, game_map: GameMap, world: World) -> List[Tuple[Action, State, int]]: +def get_states(state: State, game_map, world: World) -> List[Tuple[Action, State, int]]: states = list() states.append((Action.ROTATE_LEFT, State(state.position, state.direction.rotate_left(state.direction)), 1)) @@ -58,23 +86,25 @@ def get_states(state: State, game_map: GameMap, world: World) -> List[Tuple[Acti def build_path(node: Node): + cost = 0 actions = [node.action] parent = node.parent while parent is not None: if parent.action is not None: actions.append(parent.action) + cost += parent.cost parent = parent.parent actions.reverse() - return actions + return actions, cost def heuristic(new_node: Node, goal: Tuple[int, int]): return abs(new_node.state.position[0] - goal[0]) + abs(new_node.state.position[1] - goal[1]) -def graph_search(game_map: GameMap, start: PositionComponent, goal: tuple, world: World): +def graph_search(game_map, start: PositionComponent, goal: tuple, world: World): fringe = PriorityQueue() explored = list() @@ -88,7 +118,7 @@ def graph_search(game_map: GameMap, start: PositionComponent, goal: tuple, world while True: # No solutions found if fringe.empty(): - return [] + return [], 0 node = fringe.get() node_priority = node[0] @@ -109,7 +139,7 @@ def graph_search(game_map: GameMap, start: PositionComponent, goal: tuple, world parent=node, action=state[0], cost=(state[2] + node.cost)) - + priority = new_node.cost + heuristic(new_node, goal) if sub_state not in fringe_states and sub_state not in explored_states: fringe.put((priority, new_node)) diff --git a/survival/image.py b/survival/image.py index c1727cf..0e4ad46 100644 --- a/survival/image.py +++ b/survival/image.py @@ -4,8 +4,11 @@ import pygame class Image: - def __init__(self, filename, pos=(0, 0), scale=1): - self.texture = pygame.image.load(os.path.join('..', 'assets', filename)).convert_alpha() + def __init__(self, filename='', pos=(0, 0), scale=1, surface=None): + if surface is None: + self.texture = pygame.image.load(os.path.join('..', 'assets', filename)).convert_alpha() + else: + self.texture = surface self.image = self.texture self.origin = (0, 0) self.pos = pos diff --git a/survival/learning_utils.py b/survival/learning_utils.py new file mode 100644 index 0000000..a7cbebf --- /dev/null +++ b/survival/learning_utils.py @@ -0,0 +1,112 @@ +import numpy as np +from IPython import display +from matplotlib import pyplot as plt + +from survival.components.learning_component import LearningComponent +from survival.components.position_component import PositionComponent +from survival.enums import Direction +from survival.graph_search import Action + + +class LearningUtils: + def __init__(self): + self.plot_scores = [] + self.plot_mean_scores = [] + self.total_score = 0 + self.last_actions: [Action, [int, int]] = [] + + def add_scores(self, learning: LearningComponent, games_count: int): + self.plot_scores.append(learning.score) + self.total_score += learning.score + mean_score = self.total_score / games_count + self.plot_mean_scores.append(mean_score) + + def plot(self): + display.clear_output(wait=True) + display.display(plt.gcf()) + plt.clf() + plt.title('Training...') + plt.xlabel('Number of Games') + plt.ylabel('Score') + plt.plot(self.plot_scores) + # plt.plot(self.plot_mean_scores) + plt.ylim(ymin=0) + plt.text(len(self.plot_scores) - 1, self.plot_scores[-1], str(self.plot_scores[-1])) + # plt.text(len(self.plot_mean_scores) - 1, self.plot_mean_scores[-1], str(self.plot_mean_scores[-1])) + plt.show(block=False) + plt.pause(.1) + + def append_action(self, action: Action, pos: PositionComponent): + self.last_actions.append([action, pos.grid_position]) + + def check_last_actions(self, learning): + """ + Checks if all the last five actions were repeated and imposes the potential penalty. + :param learning: + """ + if len(self.last_actions) > 5: + self.last_actions.pop(0) + + last_action: [Action, [int, int]] = self.last_actions[0] + last_grid_pos: [int, int] = last_action[1] + + rotations = 0 + collisions = 0 + for action in self.last_actions: + if action != Action.MOVE: + rotations += 1 + else: + current_grid_pos = action[1] + if current_grid_pos[0] == last_grid_pos[0] and current_grid_pos[1] == last_grid_pos[1]: + collisions += 1 + + if rotations > 4 or collisions > 4: + learning.reward -= 2 + + +def get_state(system, player, resource): + pos: PositionComponent = system.world.component_for_entity(player, PositionComponent) + if resource is None or resource[0] is None: + res_l = False + res_r = False + res_u = False + res_d = False + else: + resource_pos: PositionComponent = system.world.component_for_entity(resource[0], PositionComponent) + res_l = resource_pos.grid_position[0] < pos.grid_position[0] + res_r = resource_pos.grid_position[0] > pos.grid_position[0] + res_u = resource_pos.grid_position[1] < pos.grid_position[1] + res_d = resource_pos.grid_position[1] > pos.grid_position[1] + + dir_l = pos.direction == Direction.LEFT + dir_r = pos.direction == Direction.RIGHT + dir_u = pos.direction == Direction.UP + dir_d = pos.direction == Direction.DOWN + + pos_l = [pos.grid_position[0] - 1, pos.grid_position[1]] + pos_r = [pos.grid_position[0] + 1, pos.grid_position[1]] + pos_u = [pos.grid_position[0], pos.grid_position[1] - 1] + pos_d = [pos.grid_position[0], pos.grid_position[1] + 1] + col_l = system.game_map.in_bounds( + pos_l) # self.game_map.is_colliding(pos_l) and self.game_map.get_entity(pos_l) is None + col_r = system.game_map.in_bounds( + pos_r) # self.game_map.is_colliding(pos_r) and self.game_map.get_entity(pos_r) is None + col_u = system.game_map.in_bounds( + pos_u) # self.game_map.is_colliding(pos_u) and self.game_map.get_entity(pos_u) is None + col_d = system.game_map.in_bounds( + pos_d) # self.game_map.is_colliding(pos_d) and self.game_map.get_entity(pos_d) is None + + state = [ + # Collision ahead + (dir_r and col_r) or (dir_l and col_l) or (dir_u and col_u) or (dir_d and col_d), + # Collision on the right + (dir_u and col_r) or (dir_r and col_d) or (dir_d and col_l) or (dir_l and col_u), + # Collision on the left + (dir_u and col_l) or (dir_l and col_d) or (dir_d and col_r) or (dir_r and col_u), + # Movement direction + dir_l, dir_r, dir_u, dir_d, + # Resource location + res_l, res_r, res_u, res_d + ] + + return np.array(state, dtype=int) diff --git a/survival/model.py b/survival/model.py new file mode 100644 index 0000000..cde9715 --- /dev/null +++ b/survival/model.py @@ -0,0 +1,78 @@ +import os + +import torch +from torch import nn, optim +import torch.nn.functional as functional + + +class LinearQNetwork(nn.Module): + def __init__(self, input_size, hidden_size, output_size, pretrained=False): + super().__init__() + self.linear_one = nn.Linear(input_size, hidden_size) + self.linear_two = nn.Linear(hidden_size, output_size) + self.pretrained = pretrained + + def forward(self, x): + x = functional.relu(self.linear_one(x)) + x = self.linear_two(x) + + return x + + def save(self, file_name='model.pth'): + model_directory = 'model' + if not os.path.exists(model_directory): + os.makedirs(model_directory) + + file_path = os.path.join(model_directory, file_name) + torch.save(self.state_dict(), file_path) + + @staticmethod + def load(input_size, hidden_size, output_size, file_name='model.pth'): + model_directory = 'model' + file_path = os.path.join(model_directory, file_name) + if os.path.isfile(file_path): + model = LinearQNetwork(input_size, hidden_size, output_size, True) + model.load_state_dict(torch.load(file_path)) + model.eval() + return model + return LinearQNetwork(11, 256, 3) + + +class QTrainer: + def __init__(self, model, lr, gamma): + self.model = model + self.lr = lr + self.gamma = gamma + self.optimizer = optim.Adam(model.parameters(), lr=self.lr) + self.criterion = nn.MSELoss() # Mean squared error + + def train_step(self, state, action, reward, next_state, done): + state = torch.tensor(state, dtype=torch.float) + next_state = torch.tensor(next_state, dtype=torch.float) + action = torch.tensor(action, dtype=torch.long) + reward = torch.tensor(reward, dtype=torch.float) + + if len(state.shape) == 1: + # reshape the state to make its values an (n, x) tuple + state = torch.unsqueeze(state, 0) + next_state = torch.unsqueeze(next_state, 0) + action = torch.unsqueeze(action, 0) + reward = torch.unsqueeze(reward, 0) + done = (done,) + + # Prediction based on simplified Bellman's equation + # Predict Q values for current state + prediction = self.model(state) + target = prediction.clone() + for idx in range(len(done)): + Q = reward[idx] + if not done[idx]: + Q = reward[idx] + self.gamma * torch.max(self.model(next_state[idx])) + # set the target of the maximum value of the action to Q + target[idx][torch.argmax(action).item()] = Q + # Apply the loss function + self.optimizer.zero_grad() + loss = self.criterion(target, prediction) + loss.backward() + + self.optimizer.step() diff --git a/survival/model/model.pth b/survival/model/model.pth new file mode 100644 index 0000000000000000000000000000000000000000..e8caec5f7e4fb4353c0f45252a64ca4aa5f5933a GIT binary patch literal 17167 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 Us#n?y{Nr{(ZtK79|NGwm2V((D3IG5A literal 0 HcmV?d00001 diff --git a/survival/model/model204games.pth b/survival/model/model204games.pth new file mode 100644 index 0000000000000000000000000000000000000000..1ec464d0a388c50d044d2df0518b3574d7845c84 GIT binary patch literal 17167 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 literal 0 HcmV?d00001 diff --git a/survival/model/modeltrained.pth b/survival/model/modeltrained.pth new file mode 100644 index 0000000000000000000000000000000000000000..574819bc83bcd067e2fa15ac8f319f8851b9a0c9 GIT binary patch literal 17167 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 literal 0 HcmV?d00001 diff --git a/survival/model/modeltrained2.pth b/survival/model/modeltrained2.pth new file mode 100644 index 0000000000000000000000000000000000000000..2037f9daaaf107053c147577579f380014bc9cf4 GIT binary patch literal 17167 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 literal 0 HcmV?d00001 diff --git a/survival/model/new_model120games.pth b/survival/model/new_model120games.pth new file mode 100644 index 0000000000000000000000000000000000000000..df2cbe97ce4f69694af9acd0fed7b57e5461642e GIT binary patch literal 17167 zcmbWf2{@Ne`!{UgWiM$X5?MotYtAcDiB^?tC2O>y&?d=V%2G)7h!$%R;XCK0v}oU@ z60MX}Dv46P-`{=zpXa%M_w(NG@gDaa#~j!7y=LZf%{AwI&zbX_&&(1>VG#iV2?>G! zE658d3V3by2?z}LGxGHc^)d`zzhSQJC;_*>g(x8(pP&sJ{Cq+KgEnoETD;lUZ?m7T zU7$~>REU7CQ;49Bivk~dL*OPqug#u8oBRy7`US2H2n`YP2@T?-8+wL?25#6Q<+<6< zJ1lU6uV<*=rY%96J;P0;LWFg6T*Uc>xA1d%t@R5L`DYQSxf_DKLYMvXp;U;di!49S zbPH3S3iwKw3e z>haTLoI+$>#twI0m!$*LatfIewkt&2 zMUvkRo*Vr_z5bGT$kczd5nl@Yrvf3myF#Y@LmuINo44?#9irzbxWz^4Z!H(1zbnKb z;NKJYH4HJ_6=L*{gs?5%hD#j9CBpW3E;APr5cn%D`H#I&O~CeVz0l}yf}U#&|JEP> z);a%w{AhgqhpabvPUc)WO5KW%@kaHvqHFL&aynH4N`BXosh4ji%X@VX_{PkE6tUYI;dtW6QeKBP=1ef6K@tI(^zYv?J zlwsEDC|F?m1SCEjA?LS!A_w1eN$Pb5|xL8B99*mF1;U6!rJkuhV?*gcH2HN;`7<|x{5?E@L@5`^R3 zGG791(dbaG4e$%4lP(nRz9e~)#@6!{?`#aqcH&;rYNA$8*^N`V;rU* zS_Hw}t1pMUT>QotGpluM#Z}2g39v3*plRH}cdg57os_@tmXI(-8Byplxu2*!>EIWhUmR z6_A_tR9_Ujsq~KF~j&+7KH$ z2?r|-*fr4~(O#+ojrFM1S+?}^kqk2QMea)maZWW=30VM(6Od_T6w%v z_ikau@-Z|p%^RLN&&9Q78bt1{9WEVNN#6@~(%L;r7+hop%z%j^c>J&$T$P z^)PRhcOsDrY{$g-xupJKC{7zU2qDg)_-e5u^cPjpZEMET^w&E0Ze<%PpTEca$o8eD zmv@3h$t2=Xc?Iu>EyH=cW!TDJQHXX)WWowjay^3wy-j6!A>RpB7janB^8w?Oir}nE zBCa(`gJpvWILbs7-CDiT-mR1H_SBLg1QOZ$gKSUu1t;!^vk{{e*&F84Y^GKp-7++l z=DyhvrrTmbxB4D_S-lYwL?=PJK?MY_lHg1h6WDRYA1aeC_g$scC>1d|{4 zUs&5}R#2zyJv=#si@d(MR&;SA<#7qs4I0aTz-sb8C83$3j;m5q~MYUejmxipriyeTb4{hw9MabzGBvizW^S;lsg$sGJ_d)JL|`nO@4gZRO!~wZ#&2A6iE^`2jM$kifn< z{`epv1C8Rsc@2LwarA5_>d{?-Sv6y@FIAMY=u*Mw(~^0pJssO>szJOY0Y!5S$&V!| zxYo{}e%)_Fm$x0I!#8KsJ6U;6+-hYu%2tg%J64hXGg*VZd0{TDG2cz30|3jSlJWNQ z+faY|6!wZfAnWyV;cf33yjipf>uqmPO@U-+($AqndT(i*Y%=JK;bHCTFaBOJASKSuTKw^p)Ph@IVGsC_YmJmZY8Utg9x|Fd>@P~yGz#g z$a30dR@~X}NLZb75)>1wnZ-AkkeRxbRF-FfCKpGe96O3v*`bR2gv8i&+ipXG!#)sM zD#>nkPQ!8aqw#e`2JV;jg}x}hj#W%08%|7tf_<9&Z)@Zgy2z8nfn9|6Y&Htb*oJGe z9q^Qa3OY&&p^w29j6ZQ0Q&XPtqP{rLw+rj&giZS}TB?JW^QVbE4H--NX6MqIx-aRw zLREbKO`XP@X2Z=%`Jk}38{W|iv>{;?FTN(o8qpstZzjXp{c~V;6pyidVh8H`yCCAT zCOc)_5<27{2ZrP(&$r%(JjvI?S7JQabKyKZA7{_CX<2fG+pogYpJMDP;W3=!9}Uj= zkP<8|1`-xG2b0RzU_n+ks4q*WXO(MUjp$yCoi&cFk(B4=ZQlU(+h5ZU#X>aUnU^!J`d%`cbWquy9z z&({M>zK!Sd{h}c#L4nmA=)irK)0mlIIuDt#`r&n zygj1a^h0epW6}tTt?wbZ$(guUHwsU$JOrL%6Y=>ZX|ny?2~r)5dVn6`{Ur+Rhh=X^>c~JU=Rq;l;qlzB2jBjG1i9Npvwy9f%1}TbjYa; z4pk|l_dQ8`amSIQ1WL0H8^&^jzJpj@DbBhNs=={Oekjm8*BVvt_=^#_J7t%iwDWw~b^+c5mrPxRd`fgfAc*xbEk z;O09-u1pkx-en)H=XaK%hk_bBox2+Yb>@Irb`vN)7Q#8nyP$abLA>2I5$-OE!iy^V z(3x0bYQraby#EZ1NSjV)HSQ<6P69C7))ZD9qvXb9xoF*wSzW0xS-H(GHuXG$3i4Od%EDx79 z%Yt=v3@CUwan=|Kn~L^;)O~U8zWN@zBhrxh)b@j3epg2&JdWa#z5{S<6cYJ%dz!yV z5=Q3Vqq*#Uv?@-)^t!E7;I#r_(|2Rh9Y<`g3LxW$a_F?aH0pG8C9(%YvD|(E*t#i# zy1E#y?JYy0s7UyeO5x^h+RE=niZYE9*)z}^_k0EJp)U6LhwmSlg`l9H$h7h#hB!m)*z06nWW0w57&1<_DgANN^q03GS4nI0V zx&=?5#PLkb4UghIFp$BawW6@{XaVn>Y62d(^N^REvl5P7bHe%Ci*VG@$JFC~J-oG; zj_ayqneS$!!$zq z8#BkgjBeU+5~f|T;g9zvP`mUEy)U5;lkKKq%y%97vuXp;PB-SvJpHlP^$;xTJq*@v zqd1wn=fVDoDjNJKraEDER7p^jJ}%Cur-w`FyMmD>TPQ5UPgHa!MhZy;1_Wb-C$G$CI)JLvHjrtsQ*J~k)_(Qldh;GDQ0x+aa{uFrl8 zrmk0EtyC1X@9H7$Tjg<*);rYR^AQVIU53GlsbpW*J#aaE9#sAabI)Ha!9Nb+Ohmyg zT5$3pDZCg1+P8P|=$##8<(qd?czL}O1v+bRgP{?t{zr&g5q1e>hMSqhi;K`IMF+^q?>Jt41^yU33JaruLsh02 zd*jVQ@Htb08MZ8!WZO+!uPnh4b0^qF&S7TC17i2S3QCS-L00)hwo}X>w$=FJ^Y14S zx%0eDYU7zP^O{N8_)SnGaSop(FJ_FV90AVt5w6wKqd_Nk;pSNi+`$iyP=DD;%NwwkTx$mO4cQ55Nx!)(NispbGH9^-a zsvx*A0OrMv=Cp?dIn9<6SaA>7lm#k~7@ zT=Eht_ITAwwqI-+>AbBD`IVdC=`T6@UiLaX>~_VELSvxv{ZaH=W6OOAn1W|}KB5{G zL>rZ}#Eq>ck;-c@wltZhxZQ^%-=;xO_A_V>NQZ4}PUEt%x-={AHB}+)j>VWOukdlP*MwkKWzJ#X3X@TB7Ss}W zARb|h%_xY!mOf>SvZJx6;5#hdJb`^#y%C-Xrohr^k?7FeOr_uKCvE%unyP1KlBe1i zX{ERuuid=@^b$&7+rE70x48gMN*7~{uP0WmO`;JqB(QAOUR;%73R&Auk}2a_NxPsb z)%SEmfkK^z1$MJ=XW3eOzWpW~dh`cYZ9j6RD$SoWggcr?LH(&T zp0T!pupZifm5395Y| zz3EQ!0p0Dfuw?00%VQU`+QNaJ!z51|8B=GhjO2`FWBU=0%W_ju}{KF(19h zEWypg0;EGujLeF_MCA8t38dvK6TRBm#3L{(>tV zKJ-^{9nNg?Ap=AYmTkXFQ<5j4M{g3EDoWsPY5*CE$p9oD#4@x%H{6Q8w35VcHD_r1 z#D1FS5o$fH>nRlq48m!ymAtesb0GcwUMMOHfHUf$aGQNXSB_teti~!Z_f&+&C?!;_ zyNu*$3QSAW$63#uKsM(!SOuNM_P%5)vtlNC%!ndf(lWA5xe{!<3NS`Yn0q6(62I5p z!Ia4TFxr-Ha}}#%pyMbS^TdmwL?_f;HWgVEMQ&@0i34}(?{ z<&hWU+>T;;;Lmy2iX-SXfk;te3#Ya^v-?2o!QRxUDaDW zxu^zwue#yW;MwrSDxPF+D1$w+>Uj0?7~(GQgbCU98`{h6Ltnb11 zOVv2Fs1m|IJm%}-<8<}3Nw~yN7ky%vGrePX;`m>xWZlWnh6`iykhVdxf4^B0zVhSI`nMYm7L50>ctUe4vUvBFCP7e1CHO8q1DO%|AU>7BgX%L#Y1kLcUT@-&xzE+%QEU)t?3&DlscUh`as=wnvS7Qp z4R1&Cbwtc{IKQd^J}L(_zS)@s$7Tw{m20E$=rMin}F-|jN&j(=FjQ-(j%Gu z?R?){@@l;1T!2nLO5m8O3?@X?VvKh;HaW?#ZR@{L6Bj93n;?O2nli8-PLVZl1$g9u zDd$xyg3uaQ-AxI*=%)`4Aq<^bZ7>S>JFnLX*0MdEhk~amsDJ=t;HE? zjxgS5WZ6Y`7NGp*Dl)ZWHhG}82~XN@qdt;hc;om2T39oM8daQxGQYcUU;7rA{V0Mv z8d?~l?*!*}SMxds+;Q5?B*yXGcGSEAxc$Q&NLgXdpMyVzueFz8w1)t`&vHS}kVHCp z^>boX_K8?V){?@&yVzKg4f;QMV3>&@Tzwu?!Irit6*o<7G(~xbG$M2?n7T!1ga7z_ zyiW2RFXleM&0?Bt)|?m&vOhvqOLyU@mI_$qbRC)^+{rt|daUmgd-t2KC!SdxkL{9Cw|VnY6;ilw_Rmd=BP#cVWyeY3}rCD_Hzz2rcjJgfXeZI8!Q( zoS427?RLhXbGkJnu9;y~IUtM|H;90|up4YLu;Fz(_>%SGf-riSI-~ougnZrUjAFiX ztqr68P=CMWOrokHMm<-;U0qAj=%+tw-i?C=lGAu6bYlR1?WXp(4&d%>d1S!K3-emP_)%h&~Cm{#u%^pTkUpMBmyD%qyp^q52sBw2*{{(@c`Ed31 z6S&69ARix_fb_~w0mO{`_odeNoVz9PG3Ev-%f@148RL39&V!ynFp>i?q zil`5&+Req?N;h!r1VuJwcMj;kn?P9<|ZXNvc%1C6zvLj^d5C`D*=M&;)H<=5wwuG&n=SIqU8{_8hhG+DMn&yM{g* zD`@9+zU@5tg)a17K#yD(VdK{+vSnW|Il%7DxW~)Kj$Y(k) zQUG#nC$c(gx1w@FW)kds7%xKGdUSq7MwdzYAvz=AoK6z{IXYAa*7LGU}pmyNw(- zEoL1!E1t&Sf^Tp#@HuXEuz~spNqRUi77gDDGbj5sFeW?~b4^rm%g!+t zuoCXr)67VDuA!cd8r1GU8(n_C(mFF+g6%vi#eO@k%1WA#VGo3uV{p?{*nL?AC#`gc zkq2>D?vqQRpBmujvYYVSLzPo`coTy6oxKgra4uBDL3cM1i*XJfV^}NbgX` zc02xfzH9t(4SFxsL9DVsNkLW82aP}u!AXdYM zb90aZg;y)kVZIFL<@mwl0U1ug>j7`Og%WpWp$OaS@S6&J`+%?Y?C5m+Eb25X07jK2 zz|%$#uJWBLS7VR}dL8><<2g0%m8L3Jyy+V(?E4JrI>Im~R{;DbPl8`bcgfy~d^=X9 z7A-Rzaa=?c?3~*GqAg7re=P#qvz#%zBMKkqO~$Ow1RAwnKdFw!2@nib3V;?R4yaeOYZE&W^DD3bngx@+s?Dk{t;M&^T&=ju) z^Lneu#gH>i1K%wnMf>44bcBOEThS;!>gaSh{+E z_}h&_sc|ZFN7h`mEXZj$=QzDZ!C&9ZBV738z`lrxpQ$}nqiJnR-G+u*l>m%R=c0hH+Ww7=5P4AgIp+QhF z8Qy8YD?Txi&K=Xl+iIxB3~%m)gSFp*1dFiy!g^rZy)ODPTAFCIyl7nW`xb4K-iL=y z=HjG{&*0s$7Wno1HGEZ7WW|^4#r4+`$labR^vxMxyuEk=CZ99K{2EiNy2xO@{d3q9 zC&9*D5@d^~|Aft}J`g{P0VXcdg5Gh+f^SI~nD(d^1(i<&`5_B+UYBt>pL4VSY$ZAr ze88byzev3AHM-Q$2$C+JCV{CNpx9poT6E`-X*=tQs`CxrR*!OK<5PPOU6xB`73@Nn zv0fmf`4SV4$HBJ#M$+eOLM9xq!rnLKkYtzvSM%)AYmOvQeo;e=PV7VCpG=P0JVFC| zpsdyuX!Mn2AI+TyK{t=0{QP*BQ?7#DBkgn~Po6ulu?t+cioy~pEzB8xo)`a8oh@2$ z9aM(2xpCVn@UY@_^6bY=dc;u{5_+D~R8JAaPGRd6Z$iOXMu`3DF2@>QzsdKtOv3|y zkus;~;<^6kM;A$`UdQE}>k#=6bE2UpAbil2dws?8QJ?82PWv<|s zD0`gQgHBNb-15*itArp~e7muQ#vMP5hMkFY+Q(j6Xbq?+<%`X~LcmS_7VS^;A(2Y1 z7$zG>x&deTqhn*3DQ$~|v$Mv)sGqrT^?@#iv@gIt&o9E2uJ2ItQHB#Yl;Pa;cEIFZAH0@)0E)}^ zz{7fb_>{Q_j4tbmWS*?nbI~AKJP#&VhE^SD|u`Z_2v(|;=qw>_*iZwy?lW07d0NmdC+q_p*fM*Ji8C1)<^R>K;aNP zltm&+yBlXkJ%PP)<8h6&0%y16EG~%E%eO4zw8M~ zcn2rf)neDR0toPZOJd%Rph(M4Cet<<_$(htJ~I~tC) z!rmUqg)OJ{!zrb`#OnKDG|N1UA3eT+S!6X6_p+Xz+Orf>7KUJm=N>RyTn6z0-TeR7 z9&2Im{k)Bnqv;gE8mJ!o9^Y(ifc~);@tofX#-^@<_s)M%@_Z-yUM!+=^?^iJSPIS_ zNPz8j6?kYv6N(0BgQkWi`?gYxHDiRhpW$U>RZ=t_-@gu(oc-vV(eYMe6n#Oc`L>lu zdJZ)@D8SXp52D`9S?s5ycfN!QL#r=#5mnZ~|eYO==`x2G+j0{%O&zhDD4@O4Rv zk`ekl9>9IoDL7Tz6u#=p<9>_2ro*4Iz*bU#+kN5ylXY+-^=@8{Hew=d`e!+q7x$bu zmYGET9_>Zv-d7}Sp*Xdhe~yGmu7s!RRg79e1S5N4gsgwp%}aA%i_OzVW9~%_ywLTE z7!L)I!Hf-9e{(9VF^~DfB^&LJ^&BPm)HemlwlDW4&mMrnmgQ0dybTFL9 zy!3D)rMlsCuy82@M-wsDR}Qs(R4_B6nS7t5$jLsr0na40xOGnx@Z6!PH2Lae{2_3G z^tTAJ3q3BP04GZ$ghPqf8wpg%CGe|n9LvnZrEZ4v9oHLwpHH*n`wrP z7adN*HNnMHQDG&iNxF~aY4f4rfFx9I8V4TzHb9~}n5=93b$#u0M%7WC9)3KXo2NF` zO3F$F9J)Tjr~!Z4pAT@cWlMuVu@smI$>OYqnJ`CwlC_epHaz$c0NyR*xwDFc_^@4! zE!9@%v|lCRw0BoA(Y*-G-K*jH=U8Z3w}RX6qmMq*Rq^aSPcSZ?4S~a-nts;Gfe!Bs zowsc{iThOqS5K~FtY%4axKs&pxB1}Sqb1h!x7#q^lULv&*Qwm5d>!tS*=!&cGhxf; zSgy@qfm6A&4~#NS!%drMsloDzi5wXFzxKTb}(bG2{%~IV&?cc%JzT76_%2W{V&u#d9w_2=0fd zw#)F@`3A{5(@%|$j7E(d1+Jr|lkTr8v$hisg2UhSVaB;jA9(perc z8!3coL#<@QJ)XW&r1(=j4%cW|@|;%5u;&{DS%tDeylAoyuJ76eHGH0YRz)D!u>3nQ zUp^Y<=@#NG_ZQgEcm$%C7ZBeLQ6kiH5;HN5aoMI!;)*g*>~I%U_o##LBX8iPtf33e zYLVx$!}M~S8jOrT#VZVmK?z$e-t00bh~nFxY3@-p)UwF>$eov@C(VpKwP^~Bt<-^0 zLfc_c8lUl#=|XK5^pOquNmhl6B*9s>lb3(X4Z7E;fYMJFa2Bkj+vi=Qfrq6yeJVs; zHt%E9*2saSv=o%C`Gr?CQ>`QZxRK(*LTh5(K=vGzl_Ac%8pDK$vuzm=J@^ow!ZQjaT@m%33*BAC0D2K+ypqdJDRUfU5vo zwfF$k6kLP-EfO%NZ6YS;ZGiT7CR8D7#9I5FDxAof!hACj$G3(jaIx!INPNGI$qNf+ zUXGE$rkD&2EG>uFxBU>lU%})!cu)4`F~f#IG{&f%PA$`BWo>4`!R+G@d_&&))_6CVEM$b|!o=7q zkDkF;Eos?E!jI?zkqj~v1i z>zYC7Y#LedToS{CW)a~v7nyIJ=76_P61nGEFkf2|jz2Ag_=9v{<38pge6y3S15v44nVO-&2oZfJcEYK5WCeJFyJeLyaSYAUe z?P-DA9-DcoisMPIRX<1wzowP*{($I{M0)78FN!9l;6;&MnkIG>7cQB*H)ppXswtWUydp;AUTtIqo4Z3W=7k(q-&D%>@pYmgz^2@!La(Mh(4$k!*+>5(iY z61H5BE9u)0<^l8I=Yuq;%<`hze$U7Rg$T$yW&^c;Jwnd{dF;YJR}ewNDu#?@$syoJA+!`Ap;<%W_V?G^xv`T;A6Gy37OPnS?AI z!~M>!BKLot##Jx7nV+u5823+O*so$M$*3=n@b=O+W=+^Vv|1ZOY$}E6$L26hR?wv# zTb#%!YJf|>u7$dhYdp&fmhk5CWVUaY21p)%48t>aL+GawYJDaXdb|o~gWd(EvfLOx zOwgj=zMO)9ng*WhsGTs)(1vssdysv*wejAIIZzN<4f8({sO`yMCaNAKLc9R0;GpUz z&vT-ncVjfKu*(r9ox4lqA`8ewVRgD+Jq=2}no-}6ER!bU4$<{;SR}s-_GCG0)_5Sm*WPe2Sl(7+5s*j^Va*(6nejEVtQ3FUrWQhK=+(vH?t#0s;h_QS**6uOYU9Cf zdH`Lw`7z|*wMCE6@hJT*7Uz0)g6_%nR{jxAc*xI?a5EIJ*7P2+I-|r6c?)tgr%OX% zuopbF&tWd4%CKS4{-kfwanx|VM06`1dDYiq$Z)1CT(~Sof1DfUIhrb>P5^&@-1mMW zP!NS7S1;kJRXoPu;U|jc%z`*2QC7L)0#nl6N_!`(fZ!rAIK;+*@%P=3J)wuGoFI(t zD<(sN+DT?d>vH_zy&B#>Od*pK??I0EYo5AlI&*0JD`>7WVxg=XbT)aShE_eQ$93XC z(Jj0*?Q-l}t`ApaM)CQAL*zy7N^(_nE*%;7nbvJFwmv!i9K19a#wA(8jGx6i2p7tM zy=TXu+piMz(K$)3evgF_5hK*kc|g~kkZgMMehMyJ>H>R*M}sq;cj7}<0vqnf({2f% zFIRUE%a8zOr9&TR&r^nauM*NQTmeUkIn1hiNSFnu$RV|Ul6%S$7R7lIfj&1T66+yQ zsRHDppEWIa$Rv7EX}mPgY|`yCjK_UFpgN9uoTf55pD zs4J6)H*(j&m$E3>#Ct1De zs%SCu4H^AHkhkUgSQ_%Zi@wi4gs#6$z|u(-D@{vi{L^UqcG(3ozfcVeR~&&@n`)kj zP8F8c%!a!bqjAy&Gn^b_V;!wJ2cM6XhadCblijZhiDPdCmZkerdtpglS+@{W9QaCq zggW6I*Q4a{`l-;}_ny@34F+<(hPXdE~YHMsn)GY!vvZjU6)gsqwE!+}&Y_(N-p~IP)95Zx)7D7sGL1 zq6Kg1cn45&%q8Z91t`NW`~6Z94X882M*Ztd5z|A5xF~dw%%LyW&V#d=0xx}vd4GS-L8z-n`4 zoR@Z)o_{AwEAYV~403+N1@dz>oK|W?TcarCJsR8!u6z!PCslh;;lc$YQIF7jcUdz=r;*FxHsh z2~{O})xDpjDZGbYp3+$M;10QcwT}je4bm4G(%5fdN~)yBpr2bHe|>Ksj@SrOx4H8% zP4x<{854_%VW&v2=>kkL<#R)7^Qejc)H7H%UBC7a>Gwgb#9 zu9H**_fYZfEF9Uuv%Y!ZIt|^zqZg0Ikf31`D2Qb`=8-4L1w5tg-O)Jd z-5%uq-iu=fl_AH;iH!YOM%oQtlTBKq@NEu%&)JGZ>=kjrqci4VT7EE?O^(MON!66O zY7M*kyLfj&5!PRQ&C58lorolBgWj7J@cgVFh~5hVMWG{BWe*RNm?uY2d8Q_Q3|dBo zyk|4Dhw}fSf1Zg)Q33ub0Dr~*M*sY^!Ti6~KP4*3Fj_j9m-yxg?=N3W?&mv$|Dy{3 zKk6bUVDZ0wG5@n0eXgzPf8&5LmHO+gck|b-;BP}0_$w}P6#VP*zt2SY*L~{pep^?$_S zk1zj@^RIIHJC6LU|Ag~bKlyi@e{K1{4q^B|A$QjQ!TBEr$ ze7xsuTo)o4D4^#YD5&c$$A{j$ajTDK&>H`(K1MryHg50@2^3lv;?GAnTC*)=by}%*?L3^p_KnZ&(d%@^H z$wR(>#N(HZb`F$wm-)MlUK>4w1IPIC1@IN|{kIEP%rrF%l=0;Q3x)*B{uB5>pq#zn zfk63xf+~arj&%-H{MQ=~+2Q}cZhYK-3u|s-%!gI_11lU7sLY487v^6fI#A`0-29{J zKe5M$1Ws@cocON-|H1x;z-s?3^2}ML#>Ro_zN75 zylr3L6n6=JKdjl}6XN-2!~=Ey(MSAI;GY!;)Y})R|Bvz5?h_QuAML=Y_JYCgl7H*D zK!bgOhQ9w^z?U%4XkXy8e-vyB_A+v^A0;k0#7vthBp~o7JN?I5s48Ilw^=ytZv#Eg z7XEEM{%vyp4}Po^mVkf;Q`qW#j1G&)K(x<9__U)7s~^UbLO~740V%8bwjVJ4&JFsZ z*&961WYNU8PuYNr&C-Aqj47XUP8s2^Ez@;)-;C57kw_#N(#^)VkGSfA!PK}s@ z_RL`D8(^WYF_uJ{oFai2$3up_0B(Gg0^s>GN=8?ezt z8FLQ>gToRN-l>z5!0E_x=pR2FHCxAV@kOU_)s9`{?Y35G?=A=QfdJ3voga?w@ugU} z3vT;lQyb&2P$Ay}V-<_Qu3m_fG33F{Cs8A}vlatwuGu)H`3|vrM zv=6o7=dcC#(!9{%arkV;Agm0X3xau1aandD1h_8;3C)G5Q=UvaH}(MYCW@>L+5nv^ z$J4%@=V_bB7BKu=N-iDmrcG+~=xekd=lPbyq^SF3OPCV=l?NcCVMvZe%m?lJHz4~& zJv4Ohhch!CP}*Hh+f~P~Y7Zo7?!`1TE4)Th-*3dn>p#-l1u{@D=_%FozDh<))Nz5q z9-98vkk;%rCABIqNP?sgnH?bl+MVNJ$1OY9vuHMKhQ`<$ZE(AiuJ|Kx>2ymv!s`Ui9yv3o?@E+ad{Edcc~LKN9EOB&PtE@CJd^ z<9-UPCnIxS!G2w3yd78d_^5yV(b7DxL5{(n*XOXNP;jTwsNY zC@dV6M5+vJKlD*&phrHuU5eAT$KzvH=cr)p-1FJh$m>|KVjlWah!ZL69@MOk=mYi z%y+sE2H67K>Iy;5Dd|1xTzdiJY7^dia2WN38SGywjvX8Q*}#VtWY%j>=p16fY3Wy{ ztosa>m@TeOY6!t_OC!>4G6N>g&p|6ZfngCXKuGeHn z+~^Ydx`=@>jxMG9Sgjj`*;F*=WwphTXY)tv|_lG&Dr>ohkZ8@Y{WMH<0q z$89*)r3gkYHbSa&D1=AO<~6>nM&F06L^$jt+3)iRwB*xqudpN(#-Bu)>uEUT*NMva z=MyslO`frrGAib$DRqGt&#ooZcx`Uo^g^oR%JcOm0b=W9ZHwbgKJFEXwbJsarB}WOp3A z_>u`*T#6yS|2)9+c6zqv1U62Y248s!G`8?EtPOfgd)AHO#XTs(z;ZdRYUMC_XfMJE z3A^E?Qbk<(b{6{F=wi-oRb!70xxu6zS}>u1Ie9;G2dloc1~ar(@YAppZIO{cStTaO|#toyW*9z5dC1{*C$ z)otUz=Dj>`)8bZg=*DvRJ<}200$$;V1H){~4{Q3yZX)=NSp!F{w}Ho$U9h@s4YZC- z<9cTsa+eRAa;fSDoI)ri=fg%r#_4c0pG|R<-Dqq&KANX>KLiyv_ru%+4VW38icKq< z;LeB<2$c%aT-$W48qk5^C3nej{UBnUJ)21G_5zEw@kA!~G7DR!(6BlTlS;SKn;#gA z<*5*X$XswVY#~9zm&h51?{MgK7reMF#M5~`2&R!@Q2xmlE?b+SPH#B)uFV4FrLmx@ zw-nwz`#`gvxZ&e>V@~OqVbQ_aLdM z5c6L;L;6boJtc{9r%$MmZwdt%_)3^F(~LnY?i;%~Q4(^3H36KW3A=v+XxyBJW2d=6 z?r~Fc*DipX4jY5hiBQ-iQ)jXvr@ z_0|h8rfLscx@tDqsE&|)mjV2|j1q-$2Tb9J+lFj}-o<%`N#qk!_7Mxz)pBBPct`>vCxzg-~u<>}OcLVy$1mW2cz>fZBU^VGIXe)~I zCc-L&C?i_&WH~Mni=*S5hv{^p&YuhRuqmsOnz9yXtYt|)XWpiziOobXQwM$BYe>Mv zb=cM`$|bBG#Kva_;GOz@XiHM0Vc;KcJzC9>R$Mkxm+%Xo@wbSsowIWwJEeJ=(?j=91h4{lx zxaHt9oD@4i!(49=Qzt>}^KeEnxnQ^!Ek*ryijgalhWO!Y9=RzM2)3td$l1`@kn{RJ zam?GuIOLYY6Nz(>{iq8Hm&fBng=)}rF=Itj70B$L^>pKNZCL+60KZOh1Lv)cRQ$IM z%(PiWKP^*2i3C|{>hY6Ynw3rtt(Z#-4$5J`)CF+Ds-9%7S4XkOPs!pL#V|Si1>5OY z4i@@3V9_T`KJQ#i2MgCin*2%fP%V)h7m2ny=vYXHHDift_ggYfVL1rpB{G_uhM2fldBy*s;GBl=UCNX5kEs@<}Bk&BrkGcLlY6Uq?FctesWmb_rf6FN4-w zUSw0g8Z3M{9t7t5!pYkcm@J2OWX76-$96$fl=Y+~8U}DV=00rHBRt1?^ z;B|Z|yuFeGZDS)q{jDG9xrlP|s^hrId{s_MSBYD+W)pPOTT}VHeyHrG$tI^o&ib!FZr0cb#HBA0#i9f_#}N&z95e!QW(hO#*Bc~8j-YUO6WnmUjT`2s!ZJRu z5&Bt=%Qj^|LA@cq2^52({wd_?ha+&pQi&VV?ZnD}OQd1yS`fNXgE>Jzag{|mh&+zM z9Gc74-3&m`enAID>tJtq2I~8c!VAtxP&vmR*1q2XTh3VG>mAA1kfj4s57R-fp&o91 zOU5Xmpfe{KGFP3$pz$gollcP!`{LoSp&{#4)=|CFD2{#%k;lBXfn@0BdI(H7X4M>A zN1OE5;=0t`#3Qq>I_F3lroQfGZPa2>Dy@&m1&QISOB2EPS0y8TzK)red=!0>#?w#o zugDW^eRhXdH;L=w|&rAd6@__xuHnkBhWWWLsqHyN!ef6&>ljCYEy{I{45-I`X)VmnIiPw2H*A5(WvDlmPMq3cEb{s@9~4Gisx*F znlNTBdO+JY+<|e=Gu}vVhk ze2&K3X~C<;$*A&3gXcBp92~NH4L`11^LAQYv1%{P#tgwtxVvByM#$EH2mjeUYQrH+ zcFw^jn@Z4X4o8EKR7})zXDvliQ83U9uhwipH?1VH?wUE4d`^NLzYh|VDY^W0y%x6G z&8H#G?ZoRsI!2~+P^E*-?4!tdn)|JX$=I5X`==ko-n*;m@A2DN>oJ@0L`hWjjw#x( z`NlHHy>|%aXypQDvx%G;`amv>N~a~YrtGESg%~_@A-<6)!1FJhsMuw`E(&~!=B_N> zS(^*X?uc`?-vcn#{SE59{Q;qO%xP9_B^tIz;U3#@@GNCJI9)kNC8A1c-u@6+T0avf zpDHKSKcmr#Re)`c2LRir06y`jQcB;+-YOTE7tg@HDnodD(vlrxy&D8JB$J~$j-Y96 zi|^-4Q`b@@Dj4{h{I0x$2Ol4W?y}kJnUV@?_~1kKpiaW<^PA6WaFE( zWYpPw9ak0R!h(+lC=!(l%ZK&ArnZF~+4h*o%BYa((|zfDw~1s`s0`Mh8jnR0cZk@r zQD}{7)b-2@B3D;NzSqB}yR7<%?1imRf9NF4+adsF3!acyevipunhW`CjMELT{U-G~#q%Sc9#7+01e40P}e*=)8F z4oZ|z=J-|e+j1GBvQ7hH&mAHDLr$#QqPrNksGWRy;LYCJ*GCqV$f+yiR|7nmJRt7^G@*qB=5<)jAa4&_j zal@K)>+Gugs0@y7#oZiyhgj1MzHtjCS1E(oXOec#RMCSP-~bN{Bi@Gzj|=jiOipl_Pm=wpZdo~peCj~%@fj*kdsADgr4$=+Qs`Sol3s~q_ zOx(v=!0t(Y5Ef8I)tr%CbUTV9on8$M4dxgy2XNKv3bMyQi0U7>i<=uy63P9xP&zml zn+-+5Bv%nU$LQ0d-5DfJH3r3B`{1*2a`5C%EV;V(C_7ckl8*H(#f_HwFv*H#dMD;6sXV(XM%Vj&_v@XJIy$zmCG3f3UiA{>aJp1JB*z%(ceoSlv z>D0RzZx9Z};u~0GN+L98t0!n0X{!}V!KWR+0a&m%I6+J{<4{{-QXo% zGb$YBUtP`J{j`QVx~8XEUS*+q>5S|6+2J9|Kf4Ec=h|V3rmj^_aXe1W%BFg!zC$g0 z1)U7KaLbLiuwCv1YRbv*(t<)<62TWC+_eX~IqEdlze;V}%B_oB4;Xs+w(IPRC# zMUd2z;`X@wLWK(|TGpA99D68axnvrU`33-6-c@4Q`_4 zdbBQp2+9aZuzXeN`h1uod6L|e!wCE8|ig$!>_vk!RfNO-0 zLQ&RA4EQ0!Q&QzD7nwTIytrH}iGEKkuB|{N=fxzA6tRaECX;8X8AMgW;A+#enP~s+ z5Pmvz0Af3~ppQ=g27Wc)i;>(H$6kpch&dwIJy2B78dKFG|<>(^-!) zF>KaZ)X^KoOM?{LwRHfJ^F?_pE>)mYo(OOG6#hD_5W&Z4=Cocy85fS4gH`=9yw%mS zQTS*ltd1AsoqDz%7p2MbR8Fho+`*f)5#+J|aWICZF2iVDJLvOSgr?zx)aT7AC@C4D z(+oE=bB``07sv5!J7v1~ai0Krv}PY2vD-=lcDyCq`!>NC%pk1tY$(#KVpfEkV%5zL zq^Q3H-+!>dU7BrVM@AaP*q?>B79B8SnKG#kxDM{4GGKP@0kR>0;1oe+EZH7`LxE#> z&m;xekc13$g3}PaJOMBDAAknYX4J6GVD$~ep!mvCko;szXK!>R*9&%nbwVDLdKR;B z83)l$V<}C(8o^vw1XgEiE@``$4=<{WKmkO+S$HgL`K1Owm(S(~zi4u?62{z>{WG|X zYJF@!f0HQoUPbx!SE02khrBY3WSXs~lY4qBsmYoJ(l65K0>%YyW*`|~>xG^nB0%An(+d9>$kmD_L+`KAt;)LaN%Sl%ej5uF z;}+uv?bU4Bn{{+E(@n2qc6Y2Nq zGVrl7oyPhASmrN4jc?Xya5{~y5dTJ-m-BFBy$3xM?TGV75+JN4fyueIhFG5nXVwW3 z^jN7yQf5s8Gpkf`N9;Vj6jp$1Le9kIa37uucH!I#r*rq;eZjj8v2^+Fv*cB_ApF>% zNEQUtlRlwqO!w+3@M&)eoh&Fv4=gYQB{YPt3IjCu6sIdwS3sxEXx9QWoE1h}RAo-YTBrImzL-#^axDv)C>wvM8J?ZHmph?Xh;4yPM z$$euD#fJp&m6s%BunIWAP#tz|x)0k5b*SxWY1p;v2>8s1#W{QSv5OWVeG^n;5EI!|JF4cqw-Wr(S5omyr`Vx%Y8!Sl}@pzq=Pt z@0KL-;_qn5u03RnMhr~XS;%$zW`X?6qxi$_D9*^dPjuzJ175Ah757Y$ToR?v6fU4a zL^TmUrvY{E9}*`SFF0eq5W_uA;(Xg1cx0(8z1VvQ$DDP;M@|ok@XKE`-C=-H_Rj|W zolj`#dtC_LCCf|KI|W@GCOp-sK%5n=11C7 z**1}<)@I6!Q&Pg<4@gsDKC+dAahO&-7vI~pQuj4eY4p#Pc)&-QUazym^&ejny7&d@ zc_)I~0+LZK>N8ZE>cYU6HdGpM!1sa|F{yADS)r!NlbCS}4!=1es4 z;v$CUK76HTe~IEf<9rC`KO@O}s)nK?4?*D8I#j(-!juj*RbTJifb*}4(fiAfG2$0S z=$p(4R9_KE!)$G=L{|5c(Te?qG4~>!*-z-RL2DSbN{zX6@eCF6HX-&`Y;eq?gZT2A zHH|Pq@?%CTtuDVySDo8R%7%Wzm6Hdta)=VEVQXAC7g6xkC~iSKi@8R3Aly(0KV}~y z+M1S_u+$d!o2B9{J(g+@-6c8OYMCkH`8xdWYobwTO>X!dr(s1KFzwd?#;0l+T_*m* zu;@$h*rObdn{=aMQx_)6q=R(zL8!wlQdS;;=e2^NsJ(#PuxciS*4a=H#)C9D7Rvd2 zLu1)_Qjw+xbA#*Y%8j0E{ZD6dC#V_cR-9p{J(1#x%(@6RpTu~(B8=dn&{Ldod8n1RqD&%)cZRCDRzEg^l~+%I-_PY}l(`#@KPn^PeRkyBg3J8-@1F1adaaY&~BnvtgWzVY90}g z|7dC1T|t{H63DN(aoE1J9Nyh+B~7{|3^U^-T-7n zyb3cnTY>chX=bw35{x~kkE>3-A>lqv;O5be-Nh8kO=Vz)Z7IgR>8|c@`2?=&T~xW+ zm0jb>u$QKfqQ)PhKvef2EQt#Td9@ILsVbastup7PF^=1tEytNUF2ak8m(VhIb2?7; z4LjOsG_#?Sw1gxz>xNJ=wIlsgj7Wxg-GG%E7qn)(gsvkEhGC!+2E>+aMWb!!XB>b%E9s=Wf9D!ygOAuU!sDF`0asNnXLFbK|0VyS`)OdE@97gSHrHoUAJmjb?zz=UFxuu%=*2aQzNGuej+QF>QObF_@ef9fu4W{?zIXdyLLyT<{4^A6@ z$2ro&`>H*dx zwVV0u_7FcR{j9c~10k#ADJKydcxFAM`5{0EbI@?u{yh(zu%Oe z3!%|Myy;?UykAabsCdhmZ{vKA1NAc8r}$iulX^@m^JU=RQ#-PJ*DH}OzGU8cR6ylm5{+`;qGkBG617i+I@YW=qW_9f?$&&kFAV)RG3|qn94{lrxDWYnieCs@QS}I0U^Li}XOG=niMn=$= ze2GRa+eJp!-(^qdAAo1yM##BpUj2;8w;rP=s*$O zFwo6xMlr9IU?RL6bH1Kns`&kA(Bnw*uPgD_-AUA#QI1Eu=Rk<)4IoyZV3)NqMz6g~ z57ZZ+-|kVMy|IoSvfoeFj|suJxw4S*GmU6%vZl^^w`0v!V`w3_$ouC9iOx4W5KP_& zPRimihvy8R1!cJ9W)t9(-5pGPF3joFz9x?+MPN)xJczs^e4iK}XgN{|bItPE@13FW0WpplE-YwI`NC%PKw5O-Wenb+3;O&1iZv|W07t%*q;ADiBSjD z4;dk%n%Yo#aSHx=v=5Zh4dJLILtLJDl6?~=kewX~aHY{6x30`0c?R2uX!DR_CYh$7sOd{GO2{5JO&y{8&D+4- z^aYB#T}Qt-J6a!b9#djgzzd}0(uZ)ovRsLtb<1OQ>QabJ*b)pEdyU(!b)c$XBSdwa zCRe0VQ1Os3?c2YBd}q`lL0ua3RU^>W$^z!wuOlxW=P*ho9x&A5gN#8Ynm!Yto-Wep~wewCcX7h5NzP4BB}n{@~A!035! z<7qy)4vr#HL7$m0`w)~bYQ&hQff)8)pSF3J;lkx^_;9`m3O$s8);$?G?fnj1%M>zy z@#ny&t`ro12JmCoL>MW(4!QDCP~-TJn5VhJmBKX;)E5I*8#9@cyCtFhehlL}s+fq+ z&_`2~V)Rqs!6gL&7`4Y7f<%PClo6$m)Rz*!pRss_Z%?+F?8g>nexUKK!(=Gl1~QZH zBJ-{Yn9V2gn0*4KH`Ia8rFs-JDudS0vAC&k8TIO%3rl?ZQ7vg2R+_~?VwVjM)r@$X zO`G6n=K;ENngkcyV2jpIm2lw5d(s)H21AXTh*Q8AXqxwi6i>{B=!CQIyQvO7%*uy= zkSWLu4aAifIw_Mg6KBj<2e#-lv9K6JKfx4OJ0J>Y7fqweR-NRsohO?m@dd^s)rvL3a~Nr z6=r-K$E6-uVcoS+E$onRGR{LZ-oJd?4lv zMSS~V#4|r+f1f1crH`=N$OTrdsX@QJ*0^)|AtPK>j3t^jY0X(zlC5!Z>ad_GTR^tujcIMPxa=HiW}HMNvJDf4V%f}$U-Xaflgyh2R)5_f zIcveU=8ciUlNS=n7ynk+U0YA>tS<4#pb$D1U4{gOeE8|JjI-!<=VlwO;*QqXaAg}J z$e0sL@qWsDG&U`zL0@&yJMkAe$C}eUd3?VzOKZrSWC&^(ldMAcd#a*AJE8PS9K_wM zW?B{vN5N~2UQi+e0UUJPPh-BozK!5&)wiSM;>k)CWFnB zvCyI(gnkX7WXy3PUSFL#-rg?4t8dx^OO`DGwTVgS@NI}_ufG5@J9Kd9!$VSkVG9(E z?P7~8Sdx|dnd&c^K$^NfL8qez-d!6B>JrvG8SMkuonVc(o+QKlN0M;1{Sqpfj3<_y zApUqZ2#kmTZ_39pytl6`dHu)FSWTaiMe5_n^V;{Uft``1Smb)1xqP+}Rx562#>JP@ z4I@V}DD)t4lTO5?S(C6tdLr(wcH@_u@mBKUP`M==(xTE~yo(}j$utFAP`4?m;4}e&Rc`KzcJ7Iq7|rE ze1S2)W66`A8+5SS1zC$FAhhfh+1g)5D?^=e;Bx}WnwbGlH)w+01ZCdhs9lgVatglP za|e~{mtkhNCnmj`##>z^&3hd-OiNBC;OV#PQ8Bd#wx_-YL9KJF-BdrgqFx7g>TP)w z{7ulTE`yd>{KaI{*3fMlY4EPMfl5IwE}N1-R@{jph7O{*C(#tGF9`5TiuO@QV;>yv z9RMov`n)S`W|&>rKu%Xp<=yRN!R|~FoSEAP9vVD);d~QS{2+suxinU%-5*!v^B`ks zFkHOT3*>Ysvu~dOZ)TY+Jh^I&Ie~3-qCP{8dcLIkt7PG(p(>iaY9I%U4q=B;Cnc|D z^VUWlLpkFD;62xYCHo$Nb*~mrsY(=EvXaTW?JpskKX>LXb!3-xHbBCUQP|)aK-4@> zu3*)(=BmtHX$#cwXg^nhx1-MeRW zk@Q$}8MNaZnqJ_grCzABH4W3-#5r}}$MnrxEmVlBU@R8@sJ{7nAvau~3ysaeY}-Lw z_NxCb^p(0xZwuv7Z|Q0p{$VL6)shEK7b4X-ELVMFTOqr#MGB=;rgH^9-!g^)b?iXn zSUkr!bH`p>i&b|exXx2W*!(je4&`*RJ^a!WJT&g5n7}wpIc+DRg7TUq-Y!z!w`b z3fWzQ(d?V1F<5?L9oD{iYxTW)H%zU$2L;>J$c!j&#&vuxo8zpCfiF9${pfEr-h2+; z)5;;UZaAS<$6xg5#2PBLwgfgie#6B|x9A5IA+G;{0jz#Ps7a6^R(x2+dR#Um&J)V% z3jZ%`SMmh>@~8}d+BstVKpdyk(u(GqanLf)#40A2VOIsdpy7tia6yx@#=m>1|F%TB zcknLtn$m;Li?ism?TOS|Q5qjL6tO-`4b@S;h^3o&%)>+TIKwH5+>#Rmh#rRQh8|Je zc+&t+hpSLA4@)l8^)UZA=RHhz5y0zF_rZI-5bB;6hL53p=|}TC`dn`i#qT9E0ZYem z-K&ha)j!0!N|R0SrA~={$`;0P`}gA4*`qKoq6)*`S<^Xj%~bxC1~#tWN{P!XWz^Mt`hge{YWEn$8?0tVbo|E^wWhlcmTEUs?)h22Y}Yi!6Un`~yvCyiKJ)4^t(+ z|7cIj2)#6>9p+3fK&9w<`lQkoHA~0i0=+NP#zi0V<`gj7M)Zs1{5{3d>5d2cTv7l{pKRxp_1@y*fpVOd=v6&s zvJo;Le^FW4l{oLEBDxm6p{;Fi(Xe3>467&86~C0&;b#)K^^GI-pD_v_DjVZ9gJ$e# zTE!izIYph_=h91Qnnqcv(8fq>YF?A$Uz%2cLh;WJ?X^ z)8;d0>7@01f1H53m`Rmswuu_*-M&fh_s`=}Hras5^pCXtfe>3cMw0R^8%`Tz~E~8q#NravkQ_<~)F#*32RzFSepd_*}d&*Ojp`uxCHD zE24XQ7V*B%Mg7H{@F zrn$4MFz0|iGyCfr{L*BDQICgNt4*_rv++_ocau4ntYv^Yi{o&l`8-Z+Z>3;oh6**V zthad=7G7vYZyQVQrqBdjt}%mqxN-(}Dxig_l)pxvTb)Mf>r&)r+*tfwl>o;_rV;zX zdpJ!?59e=jhtTxZXt{M3#`uoMO`AhWb%6|K#&+S((xXKD`$DqgWe|7RauP3BLX=Zq zFM#JS%*38;L3sD@0xt4W6usH0&%Ih+PV-wCcv+QA(_UoIv$eg9<;;obemk5TKR=yo z{WK0iIf`s(QpCM#DeQ>v3S4?m7v+Yx)6|xF?Cj>p3oeMp%d+8&x4Z_nm5OtEa{Wy8 zrbJS1D2qkU6i_E%9jdR~j>)4&fsSi9GSb?(O+SImT73vu>jKH%{V!dIek@PNi?p@bzP^3$r;l ziaoY%HlDi~PE-3d@pRv4a{SgM>ign2n|&$~t7_t@S*#(B9p}cL)$zsrEE_BqK10(! zrE%{*3Sxh&BKPf;D#nesVadK6YUnWscY5Svn65#UY@#3&e8&P^>N3gR<#u#e4N|jieq3bO z1`tuH#e4x_CUxCwR$}-qbN{n9Uh5BoTalFXo2gLS27;1K-n3G`g(@koVD%qgL5bfY zoZ3{5D#oN@^HqJuvU37%kw9=7_(_y5?&NAU-eL6XThwYmf?JZUi1NO!WJX~oSzF5Y z$-fniuKYNJ{pr4(%y(cyqysFFO`|De)UnNF0`8E0Mzt+9 zxz;Q{Trqns=IV*zd?^)HWZrfRm>EU2Gws-pd{)a%o_< z0=K@-2siYu;B4}9>G*;1So6jeUte59uzv;Gn5p0^)p>NFMZEfliyIkL>%mDcRKZJe zV#wHkMTh63QKNJQKHI2cMQf7qu+D4h;AIX>^ld6<)I~ks$#c>RN7bcfjV7AC)*wvee#gZ0SX(@-t^i;XALsEFD^&xdWf1Sv0 z3?vSNJGsI8))3Ijv7Rk1%;HjAG!OHE69dz@GfK5=(CGsZ9wkffT$u`i*DJ{ENk$Z$ zz3E4u2C3Ngl}?fu;-;i9IK@SpTr!uyB~dwyu)8CUvzmoH;@!mcLN?heF+$s?Rr39s zW0?C-rr_Cz3T#atvXWk}OLa^1aQ(dr7^*)7mF6d*P>M4Q=#5ZKrKxx;aWVK`HpCxO zztg;p*4X%?m<`U*!~2QV+}P=p8SAT8s>V+^%-HmW;;H!#=#OEckTEKN7T@*szQrhA!|YJJ}2VIsET;BK&Kdy6m5VzombFL&Cqe z{#Vfc&M^RM!+t^Xd`{_i;d$`SvLBRlOs;ry{n{X5RT^1Hv|Bu)QMIDh!%zvKMt znEf4Rg4ut-`3E=rcbtFi`9CjV_&+Jh`v2hk*LXYGi;3}VbpJ?d!iN9#O+Y~GzrOvi pW0v4QPsrQ+^T==a=RFbv=WYM_`%kfxz34xxi}73kssGQq{~vL{BbWdH literal 0 HcmV?d00001 diff --git a/survival/settings.py b/survival/settings.py index 0deb510..74dc94a 100644 --- a/survival/settings.py +++ b/survival/settings.py @@ -1,4 +1,7 @@ SCREEN_WIDTH = 1000 SCREEN_HEIGHT = 600 -RESOURCES_AMOUNT = 300 -DIRECTION_CHANGE_DELAY = 200 +RESOURCES_AMOUNT = 100 +DIRECTION_CHANGE_DELAY = 5 +PLAYER_START_POSITION = [20, 10] +STARTING_RESOURCES_AMOUNT = 10 +AGENT_VISION_RANGE = 5 diff --git a/survival/systems/automation_system.py b/survival/systems/automation_system.py index 4027fdb..4029701 100644 --- a/survival/systems/automation_system.py +++ b/survival/systems/automation_system.py @@ -7,6 +7,8 @@ from survival.components.resource_component import ResourceComponent class AutomationComponent: pass + # def __init__(self): + # self.resources = [] class AutomationSystem(esper.Processor): diff --git a/survival/systems/collection_system.py b/survival/systems/collection_system.py deleted file mode 100644 index f9069aa..0000000 --- a/survival/systems/collection_system.py +++ /dev/null @@ -1,20 +0,0 @@ -from survival import esper -from survival.components.direction_component import DirectionChangeComponent -from survival.components.moving_component import MovingComponent -from survival.components.position_component import PositionComponent -from survival.graph_search import Action -from survival.systems.pathfinding_movement_system import CollectingResourceComponent - - -class ResourceCollectionSystem(esper.Processor): - def __init__(self): - pass - - def process(self, dt): - for ent, (collect, pos) in self.world.get_components(CollectingResourceComponent, PositionComponent): - if self.world.has_component(ent, MovingComponent) or self.world.has_component(ent, DirectionChangeComponent): - continue - - if collect.action == Action.MOVE: - self.world.remove_component(ent, CollectingResourceComponent) - self.world.add_component(ent, MovingComponent()) diff --git a/survival/systems/collision_system.py b/survival/systems/collision_system.py index b9cc0f7..377a072 100644 --- a/survival/systems/collision_system.py +++ b/survival/systems/collision_system.py @@ -4,6 +4,7 @@ from survival import esper from survival.components.OnCollisionComponent import OnCollisionComponent from survival.components.moving_component import MovingComponent from survival.components.position_component import PositionComponent +from survival.components.learning_component import LearningComponent from survival.enums import Direction @@ -18,7 +19,6 @@ class CollisionSystem(esper.Processor): continue moving.checked_collision = True - vector = Direction.get_vector(pos.direction) moving.target = tuple(map(operator.add, vector, pos.grid_position)) moving.direction_vector = vector diff --git a/survival/systems/consumption_system.py b/survival/systems/consumption_system.py new file mode 100644 index 0000000..c47b521 --- /dev/null +++ b/survival/systems/consumption_system.py @@ -0,0 +1,30 @@ +from survival import esper +from survival.components.consumption_component import ConsumptionComponent +from survival.components.inventory_component import InventoryComponent +from survival.components.learning_component import LearningComponent +from survival.generators.resource_type import ResourceType + + +class ConsumptionSystem(esper.Processor): + def __init__(self, callback): + self.callback = callback + + def process(self, dt): + for ent, (cons, inventory) in self.world.get_components(ConsumptionComponent, InventoryComponent): + cons.timer -= dt + if cons.timer > 0: + continue + cons.timer = cons.timer_value + + if self.world.has_component(ent, LearningComponent): + # If no item was picked up + if cons.last_inventory_state == inventory.total_items_count(): + learning: LearningComponent = self.world.component_for_entity(ent, LearningComponent) + learning.reward = -10 + learning.done = True + cons.last_inventory_state = inventory.total_items_count() + else: + if inventory.has_item(ResourceType.FOOD): + inventory.remove_item(ResourceType.FOOD, 1) + else: + self.callback() diff --git a/survival/systems/draw_system.py b/survival/systems/draw_system.py index 38aac3e..82692c2 100644 --- a/survival/systems/draw_system.py +++ b/survival/systems/draw_system.py @@ -7,10 +7,10 @@ from survival.user_interface import UserInterface class DrawSystem(esper.Processor): def __init__(self, camera): self.camera = camera - self.ui = None + self.ui = UserInterface(self.camera.window) def initialize_interface(self, inventory): - self.ui = UserInterface(self.camera.window, inventory) + self.ui.load_inventory(inventory) def process(self, dt): for ent, (sprite, pos) in self.world.get_components(SpriteComponent, PositionComponent): diff --git a/survival/systems/input_system.py b/survival/systems/input_system.py index 1dcad25..2ce2ed6 100644 --- a/survival/systems/input_system.py +++ b/survival/systems/input_system.py @@ -24,7 +24,7 @@ class InputSystem(esper.Processor): if not self.world.has_component(ent, PathfindingComponent): target_ent = self.game_map.get_entity([int(pos[0] / 32), int(pos[1]/ 32)]) if target_ent is not None and self.world.has_component(target_ent, ResourceComponent): - self.world.add_component(ent, PathfindingComponent(pos, True)) + self.world.add_component(ent, PathfindingComponent(pos)) else: self.world.add_component(ent, PathfindingComponent(pos)) diff --git a/survival/systems/movement_system.py b/survival/systems/movement_system.py index ed2eb53..bbafd65 100644 --- a/survival/systems/movement_system.py +++ b/survival/systems/movement_system.py @@ -13,11 +13,13 @@ class MovementSystem(esper.Processor): for ent, (mov, pos, moving, sprite) in self.world.get_components(MovementComponent, PositionComponent, MovingComponent, SpriteComponent): - cost = self.map.get_cost(moving.target) - pos.position[0] += moving.direction_vector[0] * mov.speed * dt / 100 / cost - pos.position[1] += moving.direction_vector[1] * mov.speed * dt / 100 / cost - - if abs(moving.target[0] * 32 - pos.position[0]) < 0.1 * mov.speed and abs( - pos.position[1] - moving.target[1] * 32) < 0.1 * mov.speed: - pos.position = [moving.target[0] * 32, moving.target[1] * 32] - self.world.remove_component(ent, MovingComponent) + # cost = self.map.get_cost(moving.target) + # pos.position[0] += moving.direction_vector[0] * mov.speed * dt / 100 / cost + # pos.position[1] += moving.direction_vector[1] * mov.speed * dt / 100 / cost + # + # if abs(moving.target[0] * 32 - pos.position[0]) < 1 * mov.speed and abs( + # pos.position[1] - moving.target[1] * 32) < 1 * mov.speed: + # pos.position = [moving.target[0] * 32, moving.target[1] * 32] + # self.world.remove_component(ent, MovingComponent) + pos.position = [moving.target[0] * 32, moving.target[1] * 32] + self.world.remove_component(ent, MovingComponent) diff --git a/survival/systems/neural_system.py b/survival/systems/neural_system.py new file mode 100644 index 0000000..c2551db --- /dev/null +++ b/survival/systems/neural_system.py @@ -0,0 +1,120 @@ +import random +from collections import deque + +import torch + +from survival import esper, GameMap +from survival.components.direction_component import DirectionChangeComponent +from survival.components.inventory_component import InventoryComponent +from survival.components.moving_component import MovingComponent +from survival.components.position_component import PositionComponent +from survival.components.learning_component import LearningComponent +from survival.components.time_component import TimeComponent +from survival.graph_search import Action +from survival.learning_utils import get_state, LearningUtils +from survival.model import LinearQNetwork, QTrainer + +MAX_MEMORY = 100_000 +BATCH_SIZE = 1000 +LR = 0.001 +LEARN = True + + +class NeuralSystem(esper.Processor): + def __init__(self, game_map: GameMap, callback): + self.game_map = game_map + self.reset_game = callback + self.n_games = 0 # number of games played + self.starting_epsilon = 100 + self.epsilon = 0 # controlls the randomness + self.gamma = 0.9 # discount rate + self.memory = deque(maxlen=MAX_MEMORY) # exceeding memory removes the left elements to make more space + self.model = LinearQNetwork.load(11, 256, 3) + if self.model.pretrained: + self.starting_epsilon = -1 + self.trainer = QTrainer(self.model, lr=LR, gamma=self.gamma) + self.utils = LearningUtils() + + def remember(self, state, action, reward, next_state, done): + self.memory.append((state, action, reward, next_state, done)) + + def train_short_memory(self, state, action, reward, next_state, done): + self.trainer.train_step(state, action, reward, next_state, done) + + def train_long_memory(self): + if len(self.memory) > BATCH_SIZE: + mini_sample = random.sample(self.memory, BATCH_SIZE) + else: + mini_sample = self.memory + states, actions, rewards, next_states, dones = zip(*mini_sample) + self.trainer.train_step(states, actions, rewards, next_states, dones) + + def get_action(self, state): + self.epsilon = self.starting_epsilon - self.n_games + final_move = [0, 0, 0] + if random.randint(0, 200) < self.epsilon: + move = random.randint(0, 2) + final_move[move] = 1 + else: + state_zero = torch.tensor(state, dtype=torch.float) + prediction = self.model(state_zero) + move = torch.argmax(prediction).item() + final_move[move] = 1 + + return final_move + + def process(self, dt): + for ent, (pos, inventory, time, learning) in self.world.get_components(PositionComponent, InventoryComponent, + TimeComponent, LearningComponent): + if not learning.made_step: + learning.reset() + + # Get the closest resource | [entity, path, cost] + resource: [int, list, int] = self.game_map.find_nearest_resource(self.world, ent, pos) + + # Get current entity state + old_state = get_state(self, ent, resource) + # Predict the action + action = self.get_action(old_state) + # Save the action + learning.load_step(old_state, action, resource) + # Perform the action + act = Action.perform(self.world, ent, Action.from_array(action)) + self.utils.append_action(act, pos) + continue + + # Wait for the action to complete + if self.world.has_component(ent, DirectionChangeComponent) or self.world.has_component(ent, + MovingComponent): + continue + + self.utils.check_last_actions(learning) + + resource = learning.resource + if resource is None or not self.world.entity_exists(resource[0]): + # Find a new resource if no resource was found or the last one was consumed + resource = self.game_map.find_nearest_resource(self.world, ent, pos) + + # Get new state + new_state = get_state(self, ent, resource) + # Train agent's memory + self.train_short_memory(learning.old_state, learning.action, learning.reward, new_state, learning.done) + self.remember(learning.old_state, learning.action, learning.reward, new_state, learning.done) + + learning.made_step = False + + if learning.done: + self.n_games += 1 + if LEARN: + self.train_long_memory() + if learning.score > learning.record: + learning.record = learning.score + if LEARN: + self.model.save() + + print('Game', self.n_games, 'Score', learning.score, 'Record', learning.record) + self.utils.add_scores(learning, self.n_games) + learning.score = 0 + self.utils.plot() + + self.reset_game() diff --git a/survival/systems/pathfinding_movement_system.py b/survival/systems/pathfinding_movement_system.py index d20ad08..0c34ebc 100644 --- a/survival/systems/pathfinding_movement_system.py +++ b/survival/systems/pathfinding_movement_system.py @@ -8,11 +8,6 @@ from survival.graph_search import graph_search, Action from survival.systems.input_system import PathfindingComponent -class CollectingResourceComponent: - def __init__(self, action): - self.action = action - - class PathfindingMovementSystem(esper.Processor): def __init__(self, game_map): self.game_map = game_map @@ -21,17 +16,12 @@ class PathfindingMovementSystem(esper.Processor): for ent, (pos, pathfinding, movement) in self.world.get_components(PositionComponent, PathfindingComponent, MovementComponent): if pathfinding.path is None: - pathfinding.path = graph_search(self.game_map, pos, pathfinding.target_grid_pos, self.world) + pathfinding.path, cost = graph_search(self.game_map, pos, pathfinding.target_grid_pos, self.world) if len(pathfinding.path) < 1: self.world.remove_component(ent, PathfindingComponent) continue - if pathfinding.searching_for_resource and len(pathfinding.path) == 1: - self.world.add_component(ent, CollectingResourceComponent(pathfinding.path.pop(0))) - self.world.remove_component(ent, PathfindingComponent) - continue - if self.world.has_component(ent, MovingComponent) or self.world.has_component(ent, DirectionChangeComponent): continue diff --git a/survival/systems/vision_system.py b/survival/systems/vision_system.py new file mode 100644 index 0000000..1a35856 --- /dev/null +++ b/survival/systems/vision_system.py @@ -0,0 +1,18 @@ +from survival import esper +from survival.components.position_component import PositionComponent +from survival.components.vision_component import VisionComponent + + +class VisionSystem(esper.Processor): + def __init__(self, camera): + self.camera = camera + + def process(self, dt): + pos: PositionComponent + vision: VisionComponent + for ent, (pos, vision) in self.world.get_components(PositionComponent, VisionComponent): + vision.update_positions(pos.position) + self.camera.window.blit(vision.surface_l, self.camera.apply(vision.l_pos)) + self.camera.window.blit(vision.surface_r, self.camera.apply(vision.r_pos)) + self.camera.window.blit(vision.surface_t, self.camera.apply(vision.t_pos)) + self.camera.window.blit(vision.surface_b, self.camera.apply(vision.b_pos)) diff --git a/survival/user_interface.py b/survival/user_interface.py index 4c0fc6d..0bf66b9 100644 --- a/survival/user_interface.py +++ b/survival/user_interface.py @@ -7,13 +7,13 @@ from survival.image import Image class UserInterface: - def __init__(self, window, inventory: InventoryComponent): + def __init__(self, window): self.width = settings.SCREEN_WIDTH self.height = settings.SCREEN_HEIGHT self.window = window self.pos = (self.width - 240, 50) self.scale = 2 - self.inventory = inventory + self.inventory: InventoryComponent = None self.images = { ResourceType.FOOD: Image('apple.png', self.pos, self.scale), ResourceType.WATER: Image('water.png', self.pos, self.scale), @@ -26,6 +26,9 @@ class UserInterface: self.slot_image = Image('ui.png', self.pos, scale=2) self.font = pygame.font.SysFont('Comic Sans MS', 20) + def load_inventory(self, inventory: InventoryComponent): + self.inventory = inventory + def update(self): pass