Skip to content
This repository has been archived by the owner on Aug 7, 2024. It is now read-only.

Commit

Permalink
feat: Implement backpack (#1287)
Browse files Browse the repository at this point in the history
* Implement backpack logic

* Serialise backpack and update artefact serialisation

* Run Black

* Start implement log collector

* Show both worker and avatar logs to console

* Add log collector and socketio tests

* Create BACKPACK_SIZE attribute

* Merge branch 'development' into implement_backpack

# Conflicts:
#	aimmo/__init__.py

* Fix test and type backpack

* Bring back previous PR changes

* Move things around

* Fix tests

* Move log clearing to game runner and avatar manager

* Add LogCollector docstring

* Run Black

* Refactor Mock classes and use pytest

* Merge in_backpack and pickup_action_applied

* Make worker logs a list too

* rewrite ui for console log to better distinguish individual logs

Signed-off-by: Niket Shah <masterniket@gmail.com>

* Use core-js@3

Signed-off-by: Niket Shah <masterniket@gmail.com>

* remove debugging console logs

Signed-off-by: Niket Shah <masterniket@gmail.com>

* use core-js@3

Signed-off-by: Niket Shah <masterniket@gmail.com>

* Fix and add tests for new console log implementation

Signed-off-by: Niket Shah <masterniket@gmail.com>

* remove horizontal scroll overflow check for console logs

Signed-off-by: Niket Shah <masterniket@gmail.com>

* refactor overflow detecting logic

Signed-off-by: Niket Shah <masterniket@gmail.com>

* Remove obsolete snapshot test
  • Loading branch information
faucomte97 authored Mar 2, 2020
1 parent 38b7617 commit 372406c
Show file tree
Hide file tree
Showing 56 changed files with 1,855 additions and 245 deletions.
4 changes: 2 additions & 2 deletions aimmo-game-worker/avatar_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ def process_avatar_turn(self, world_map, avatar_state, src_code):
self.avatar, avatar_updated = self.code_updater.update_avatar(src_code)

if self.avatar:
action = self.run_users_code(world_map, avatar_state, src_code)
action = self.run_users_code(world_map, avatar_state)
else:
action = WaitAction().serialise()

Expand All @@ -58,7 +58,7 @@ def process_avatar_turn(self, world_map, avatar_state, src_code):

return {"action": action, "log": output_log, "avatar_updated": avatar_updated}

def run_users_code(self, world_map, avatar_state, src_code):
def run_users_code(self, world_map, avatar_state):
try:
action = self.decide_action(world_map, avatar_state)
self.print_logs()
Expand Down
2 changes: 1 addition & 1 deletion aimmo-game-worker/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ async def process_turn(request):
location=data["avatar_state"]["location"],
score=data["avatar_state"]["score"],
health=data["avatar_state"]["health"],
number_of_artefacts=data["avatar_state"]["number_of_artefacts"],
backpack=data["avatar_state"]["backpack"],
)

response = avatar_runner.process_avatar_turn(world_map, avatar_state, code)
Expand Down
4 changes: 2 additions & 2 deletions aimmo-game-worker/simulation/avatar_state.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@


class AvatarState(object):
def __init__(self, location, health, score, number_of_artefacts):
def __init__(self, location, health, score, backpack):
self.location = Location(**location)
self.health = health
self.score = score
self.number_of_artefacts = number_of_artefacts
self.backpack = backpack
2 changes: 1 addition & 1 deletion aimmo-game-worker/simulation/world_map.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ def __init__(self, location, avatar=None, **kwargs):
location=avatar["location"],
score=avatar["score"],
health=avatar["health"],
number_of_artefacts=avatar["number_of_artefacts"],
backpack=avatar["backpack"],
)
for (key, value) in kwargs.items():
setattr(self, key, value)
Expand Down
2 changes: 1 addition & 1 deletion aimmo-game-worker/tests/tests_simulation/test_world_map.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ class TestWorldMap(TestCase):
"location": {"x": 0, "y": 0},
"health": True,
"score": 3,
"number_of_artefacts": 0,
"backpack": [],
"events": [],
}

Expand Down
10 changes: 6 additions & 4 deletions aimmo-game/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
from simulation import map_generator
from simulation.django_communicator import DjangoCommunicator
from simulation.game_runner import GameRunner
from simulation.log_collector import LogCollector

LOGGER = logging.getLogger(__name__)
logging.basicConfig(level=logging.INFO)
Expand Down Expand Up @@ -91,6 +92,7 @@ def __init__(
self.register_endpoints()
self.worker_manager = worker_manager
self.game_state = game_state
self.log_collector = LogCollector(worker_manager, game_state.avatar_manager)

async def async_map(self, func, iterable_args):
futures = [func(arg) for arg in iterable_args]
Expand Down Expand Up @@ -192,13 +194,13 @@ def should_send_logs(logs):
return bool(logs)

session_data = await self.socketio_server.get_session(sid)
worker = self.worker_manager.player_id_to_worker[session_data["id"]]
avatar_logs = worker.log

if should_send_logs(avatar_logs):
player_logs = self.log_collector.collect_logs(session_data["id"])

if should_send_logs(player_logs):
await self.socketio_server.emit(
"log",
{"message": avatar_logs, "turn_count": self.game_state.turn_count},
{"message": player_logs, "turn_count": self.game_state.turn_count},
room=sid,
)

Expand Down
8 changes: 6 additions & 2 deletions aimmo-game/simulation/action.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,17 +76,21 @@ def __init__(self, avatar):

def _is_legal(self, world_map):
current_cell = world_map.get_cell(self.avatar.location)
return isinstance(current_cell.interactable, Artefact)
cell_has_artefact = issubclass(type(current_cell.interactable), Artefact)
return cell_has_artefact and self.avatar.backpack_has_space()

def _apply(self, world_map):
current_cell = world_map.get_cell(self.avatar.location)
current_cell.interactable.pickup_action_applied = True
current_cell.interactable.in_backpack = True
self.avatar.add_event(PickedUpEvent(current_cell.interactable.serialize()))
self.avatar.clear_action()

def _reject(self):
self.avatar.add_event(FailedPickupEvent())
self.avatar.clear_action()
self.avatar.logs.append(
"Uh oh! Your backpack is full! 🎒 Please drop something."
)


class MoveAction(Action):
Expand Down
4 changes: 4 additions & 0 deletions aimmo-game/simulation/avatar/avatar_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ def get_avatar(self, user_id):
def remove_avatar(self, user_id):
del self.avatars_by_id[user_id]

def clear_all_avatar_logs(self):
for avatar in self.avatars:
avatar.clear_logs()

@property
def avatars(self):
return list(self.avatars_by_id.values())
Expand Down
18 changes: 15 additions & 3 deletions aimmo-game/simulation/avatar/avatar_wrapper.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
import logging
from typing import TYPE_CHECKING, List

from simulation.action import ACTIONS, MoveAction, WaitAction
from simulation.direction import Direction

LOGGER = logging.getLogger(__name__)

if TYPE_CHECKING:
from simulation.interactables.pickups.artefact import Artefact


class AvatarWrapper(object):
"""
Expand All @@ -19,13 +23,15 @@ def __init__(self, player_id, initial_location, avatar_appearance):
self.orientation = "north"
self.health = 5
self.score = 0
self.number_of_artefacts = 0
self.backpack: "List[Artefact]" = []
self.BACKPACK_SIZE = 10
self.events = []
self.avatar_appearance = avatar_appearance
self.effects = set()
self.resistance = 0
self.attack_strength = 1
self.fog_of_war_modifier = 0
self.logs = []
self._action = None

def update_effects(self):
Expand Down Expand Up @@ -90,6 +96,9 @@ def decide_action(self, serialized_action):
def clear_action(self):
self._action = None

def clear_logs(self):
self.logs = []

def die(self, respawn_location):
# TODO: extract settings for health and score loss on death
self.health = 5
Expand All @@ -104,15 +113,18 @@ def damage(self, amount):
self.health -= applied_dmg
return applied_dmg

def backpack_has_space(self):
return len(self.backpack) < self.BACKPACK_SIZE

def serialize(self):
return {
"health": self.health,
"location": self.location.serialize(),
"score": self.score,
"id": self.player_id,
"orientation": self.orientation,
"number_of_artefacts": self.number_of_artefacts,
"backpack": [artefact.serialize() for artefact in self.backpack],
}

def __repr__(self):
return f"Avatar(id={self.player_id}, location={self.location}, health={self.health}, score={self.score}, number of artefacts={self.number_of_artefacts})"
return f"Avatar(id={self.player_id}, location={self.location}, health={self.health}, score={self.score}, backpack={[artefact.serialize() for artefact in self.backpack]})"
1 change: 1 addition & 0 deletions aimmo-game/simulation/game_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ async def update(self):
self.worker_manager.get_player_id_to_serialized_actions()
)
self.worker_manager.clear_logs()
self.game_state.avatar_manager.clear_all_avatar_logs()
self.game_state.turn_count += 1

async def run(self):
Expand Down
8 changes: 4 additions & 4 deletions aimmo-game/simulation/interactables/conditions.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,13 @@ def avatar_on_cell(turn_state: TurnState):
return turn_state.interactable_cell.avatar is not None


def pickup_action_applied(turn_state: TurnState):
def in_backpack(turn_state: TurnState):
"""
Checks if the interactable has had the `PickupAction` applied to it.
Checks if the interactable is in a backpack.
The `pickup_action_applied` should be set to True by the `PickupAction`.
The `in_backpack` attribute should be set to True by the `PickupAction`.
"""
try:
return turn_state.interactable_cell.interactable.pickup_action_applied
return turn_state.interactable_cell.interactable.in_backpack
except Exception:
return False
13 changes: 9 additions & 4 deletions aimmo-game/simulation/interactables/effects.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,11 @@ def remove(self):
# if the effect is temporary, it should be undone here
```
"""
import math
from abc import ABCMeta, abstractmethod
from typing import TYPE_CHECKING

if TYPE_CHECKING:
from simulation.avatar.avatar_wrapper import AvatarWrapper
from simulation.interactables.interactable import _Interactable

DEFAULT_EFFECT_TIME = 10

Expand All @@ -40,8 +39,14 @@ class _Effect(object):
Base effect class, does nothing on its own.
"""

def __init__(self, recipient: "AvatarWrapper", duration=DEFAULT_EFFECT_TIME):
def __init__(
self,
recipient: "AvatarWrapper",
interactable: "_Interactable",
duration=DEFAULT_EFFECT_TIME,
):
self._recipient = recipient
self._interactable = interactable
self.is_expired = False
self._time_remaining = duration
try:
Expand Down Expand Up @@ -113,7 +118,7 @@ def __repr__(self):
class ArtefactEffect(_Effect):
def __init__(self, *args):
super(ArtefactEffect, self).__init__(duration=1, *args)
self._recipient.number_of_artefacts += 1
self._recipient.backpack.append(self._interactable)

def remove(self):
super(ArtefactEffect, self).remove()
Expand Down
2 changes: 1 addition & 1 deletion aimmo-game/simulation/interactables/interactable.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ def apply(self):
self.targets = self.get_targets()
for effect in self.effects:
for target in self.targets:
effect(target)
effect(target, self)

if self.delete_after_effects_applied:
self.delete()
Expand Down
19 changes: 12 additions & 7 deletions aimmo-game/simulation/interactables/pickups/artefact.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from simulation.interactables.conditions import avatar_on_cell, pickup_action_applied
from simulation.interactables.conditions import avatar_on_cell, in_backpack
from simulation.interactables.effects import ArtefactEffect
from simulation.interactables.interactable import _Interactable

Expand All @@ -7,8 +7,8 @@ class Artefact(_Interactable):
def __init__(self, cell):
super(Artefact, self).__init__(cell)
self.delete_after_effects_applied = True
self.pickup_action_applied = False
self.conditions = [avatar_on_cell, pickup_action_applied]
self.in_backpack = False
self.conditions = [avatar_on_cell, in_backpack]
self.effects.append(ArtefactEffect)

def get_targets(self):
Expand All @@ -18,7 +18,12 @@ def __repr__(self):
return "Artefact(Location={})".format(self.cell.location)

def serialize(self):
return {
"type": "artefact",
"location": {"x": self.cell.location.x, "y": self.cell.location.y},
}
serialized_artefact = {"type": "artefact"}

if not self.in_backpack:
serialized_artefact["location"] = {
"x": self.cell.location.x,
"y": self.cell.location.y,
}

return serialized_artefact
27 changes: 27 additions & 0 deletions aimmo-game/simulation/log_collector.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
class LogCollector:
"""
This class aggregates:
- the worker logs (logs coming from the worker)
- the avatar logs (logs outputted by the game under certain conditions)
These logs are concatenated to form the `player_logs`.
"""

def __init__(self, worker_manager, avatar_manager):
super(LogCollector, self).__init__()

self.worker_manager = worker_manager
self.avatar_manager = avatar_manager

def collect_logs(self, user_id):
worker = self.worker_manager.player_id_to_worker[user_id]
avatar = self.avatar_manager.get_avatar(user_id)

player_logs = ""
for worker_log in worker.logs:
player_logs += worker_log

if len(avatar.logs) > 0:
player_logs += "\n"
player_logs += "\n".join(avatar.logs)

return player_logs
3 changes: 2 additions & 1 deletion aimmo-game/simulation/worker_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@ async def fetch_all_worker_data(self, player_id_to_game_state):

try:
return await asyncio.wait_for(
asyncio.gather(*requests, return_exceptions=True), WORKER_TIMEOUT_TIME_SECONDS
asyncio.gather(*requests, return_exceptions=True),
WORKER_TIMEOUT_TIME_SECONDS,
)
except futures.TimeoutError:
LOGGER.warning("Fetching workers data timed out")
Expand Down
10 changes: 6 additions & 4 deletions aimmo-game/simulation/workers/worker.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

class Worker(object):
def __init__(self, player_id, game_port):
self.log = None
self.logs = []
self.player_id = player_id
self.game_port = game_port
self.code = None
Expand All @@ -18,7 +18,7 @@ def __init__(self, player_id, game_port):
self.ready = False

def _set_defaults(self):
self.log = None
self.logs = []
self.serialized_action = None
self.has_code_updated = False

Expand All @@ -39,11 +39,13 @@ async def fetch_data(self, state_view):
response = await session.post(f"{self.url}/turn/", json=data)
data = await response.json()
self.serialized_action = data["action"]
self.log = data["log"]
self.logs = data["log"]
self.has_code_updated = data["avatar_updated"]
self.ready = True
except (ClientResponseError, ServerDisconnectedError):
LOGGER.info("ClientResponseError, ServerDisconnectedError: Could not connect to worker, probably not ready yet")
LOGGER.info(
"ClientResponseError, ServerDisconnectedError: Could not connect to worker, probably not ready yet"
)
self._set_defaults()
except CancelledError as e:
LOGGER.error("CancelledError: Worker took too long to respond")
Expand Down
Loading

0 comments on commit 372406c

Please sign in to comment.