Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

LNX-188 Add Drop Action #28

Merged
merged 7 commits into from
Sep 14, 2023
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions lynx/common/actions/common_requirements.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,3 +70,6 @@ def any_object_on_square_has_all_given_tags(scene: Scene, position: Vector, give
return True
return False

def has_something_in_inventory(scene: 'Scene', object_id: int) -> bool:
filip-szweda marked this conversation as resolved.
Show resolved Hide resolved
object: Object = scene.get_object_by_id(object_id)
return bool(object.inventory)
54 changes: 54 additions & 0 deletions lynx/common/actions/drop.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
from dataclasses import dataclass

from lynx.common.actions.action import Action
from lynx.common.actions.common_requirements import CommonRequirements
from lynx.common.actions.create_object import CreateObject
from lynx.common.actions.update_resources import UpdateResources
from lynx.common.object import Object
from lynx.common.vector import Vector


@dataclass
class Drop(Action):
"""
Action used to empty inventory of Agent.
If target_position is equal to drop_area_position, we should increase global points and resources.
If target_position is different than drop_area_position then we should create each object from inventory on chosen
square (target_position)
"""
object_id: int = -1
target_position: Vector = Vector(0, 1)

def drop_in_drop_area(self, scene: 'Scene', player_name: str, inventory: dict):
EasyJB marked this conversation as resolved.
Show resolved Hide resolved
scene.update_resources_of_player(player_name, inventory)
update_resources_action = UpdateResources(player_name, inventory)
scene.add_to_pending_actions(update_resources_action.serialize())

def drop_in_overworld(self, scene: 'Scene', inventory: dict):
EasyJB marked this conversation as resolved.
Show resolved Hide resolved
for objects_to_drop in inventory:
for object_to_drop in range(inventory[objects_to_drop]):
object_created = Object(id=scene.generate_id(),
name=objects_to_drop,
tags=['pushable', 'pickable'],
position=self.target_position)
create_action = CreateObject(object_created.serialize())
scene.add_to_pending_actions(create_action.serialize())


def apply(self, scene: 'Scene') -> None:
agent: Object = scene.get_object_by_id(self.object_id)
player_name = agent.owner
if self.target_position == scene.get_drop_area_of_a_player(player_name):
EasyJB marked this conversation as resolved.
Show resolved Hide resolved
self.drop_in_drop_area(scene, player_name, agent.inventory)
else:
self.drop_in_overworld(scene, agent.inventory)

agent.inventory = {}

def satisfies_requirements(self, scene: 'Scene') -> bool:
agent: Object = scene.get_object_by_id(self.object_id)

return CommonRequirements.is_in_range(scene, self.object_id, self.target_position, 1) \
and (CommonRequirements.is_walkable(scene, self.target_position)
or scene.get_drop_area_of_a_player(agent.owner) == self.target_position) \
and CommonRequirements.has_something_in_inventory(scene, self.object_id)
20 changes: 20 additions & 0 deletions lynx/common/actions/update_resources.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
from dataclasses import dataclass
from typing import Dict
from dataclasses import field

from lynx.common.actions.action import Action


@dataclass
class UpdateResources(Action):
filip-szweda marked this conversation as resolved.
Show resolved Hide resolved
"""
Simple action for indicating that we should update resource view in the front-end.
"""
user_name: str = ""
points_updated: Dict[str, int] = field(default_factory=dict)

def satisfies_requirements(self, scene: 'Scene') -> bool:
return True

def apply(self, scene: 'Scene') -> None:
pass
3 changes: 3 additions & 0 deletions lynx/common/enitity.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ def deserialize(cls, json_string: str) -> Entity:
from lynx.common.actions.remove_object import RemoveObject
from lynx.common.actions.print import Print
from lynx.common.actions.take import Take
from lynx.common.actions.drop import Drop
from lynx.common.actions.update_resources import UpdateResources
from lynx.common.player import Player

exported_entity = cls._Exported.deserialize(json_string)
entity_type = locals()[exported_entity.type]
Expand Down
14 changes: 14 additions & 0 deletions lynx/common/player.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
from typing import Dict, NoReturn, Optional

from lynx.common.square import Square
from lynx.common.object import *
from lynx.common.serializable import Serializable
from lynx.common.vector import Vector
from lynx.common.serializable import Serializable

@dataclass
class Player(Serializable):
player_id: str = field(default_factory=str)
player_resources: Dict[str, int] = field(default_factory=dict)
drop_area: Vector = field(default_factory=Vector)

29 changes: 25 additions & 4 deletions lynx/common/scene.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,13 @@
from lynx.common.serializable import Serializable
from lynx.common.vector import Vector
from lynx.common.actions.create_object import CreateObject
from lynx.common.player import Player
import random


@dataclass
class Scene(Serializable):
players: List[str] = field(default_factory=list)
players: List[Player] = field(default_factory=list)
entities: List[Entity] = field(default_factory=list)
pending_actions: List[str] = field(default_factory=list) # Transformations which occur, during other transformations (e.g. chop -> Create logs)
_square_position_map: Dict[Vector, Square] = field(default_factory=dict)
Expand Down Expand Up @@ -66,11 +67,12 @@ def remove_object(self, object: Object) -> NoReturn:
def add_to_pending_actions(self, action: str) -> NoReturn:
self.pending_actions.append(action)

def is_player_new(self, player: str) -> bool:
return player not in self.players
def is_player_new(self, player_id: str) -> bool:
players_id = [player.player_id for player in self.players]
return player_id not in players_id

def add_player(self, player: str) -> None:
self.players.append(player)
self.players.append(Player(player_id=player, player_resources={"Wood": 0, "Stone": 0}, drop_area=None))

def is_world_created(self) -> bool:
return bool(self.entities)
Expand All @@ -88,3 +90,22 @@ def generate_drop_area(self, player: str) -> None:
drop_area = Object(name="DropArea", id=self.generate_id(), position=position, owner=player)
create_drop_area = CreateObject(drop_area.serialize())
self.add_to_pending_actions(create_drop_area.serialize())
player_object = self.get_player(player)
player_object.drop_area = position

def get_player(self, player_id: str) -> Optional[Player]:
for player in self.players:
if player.player_id == player_id:
return player
return None

def get_drop_area_of_a_player(self, player_id: str) -> Optional[Vector]:
for player in self.players:
if player.player_id == player_id:
return player.drop_area
return None

def update_resources_of_player(self, player_id: str, inventory: Dict):
EasyJB marked this conversation as resolved.
Show resolved Hide resolved
player = self.get_player(player_id)
for object_name, count in inventory.items():
player.player_resources[object_name] += count
133 changes: 133 additions & 0 deletions tests/drop_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
import random
from typing import NoReturn

from lynx.common.actions.action import Action
from lynx.common.actions.drop import Drop
from lynx.common.object import Object
from lynx.common.player import Player
from lynx.common.scene import Scene
from lynx.common.vector import Vector
from lynx.common.actions.update_resources import UpdateResources

class TestDropSerialization:
expected_serialization_drop = '{"type": "Drop", "attributes": {"object_id": 1, "target_position": {"x": 1, "y": 0}}}'

def test_success_serialization(self) -> NoReturn:
EasyJB marked this conversation as resolved.
Show resolved Hide resolved
serialized_drop = Drop(object_id=1, target_position=Vector(1, 0)).serialize()

assert self.expected_serialization_drop == serialized_drop

def test_success_deserialization(self):
expected_drop = Drop(object_id=1, target_position=Vector(1, 0))
dummy_drop = Drop.deserialize(self.expected_serialization_drop)

assert dummy_drop == expected_drop

class TestDropApply:
def test_success_apply(self):
EasyJB marked this conversation as resolved.
Show resolved Hide resolved
random.seed(1222)
expected_scene = Scene(players=[Player(player_id="dummy", player_resources={"Wood": 0, "Stone": 0}, drop_area=Vector(5, 5))])
expected_dummy_object = Object(id=1, name="dummy", owner="dummy", position=Vector(5, 5))
expected_dummy_drop = Drop(target_position=Vector(5, 6), object_id=1)
expected_dummy_object2 = Object(id=expected_scene.generate_id(), name="Wood", position=Vector(5, 6),
tags=['pushable', 'pickable'])
expected_scene.add_entity(expected_dummy_object)
expected_scene.add_entity(expected_dummy_drop)
expected_scene.add_entity(expected_dummy_object2)

random.seed(1222)
EasyJB marked this conversation as resolved.
Show resolved Hide resolved
scene = Scene(players=[Player(player_id="dummy", player_resources={"Wood": 0, "Stone": 0}, drop_area=Vector(5, 5))])
dummy_object = Object(id=1, name="dummy", owner="dummy", position=Vector(5, 5), inventory={"Wood": 1})
dummy_drop = Drop(target_position=Vector(5, 6), object_id=1)
scene.add_entity(dummy_object)
scene.add_entity(dummy_drop)

dummy_drop.apply(scene)
for action in scene.pending_actions:
Action.deserialize(action).apply(scene)
scene.pending_actions.clear()

assert scene == expected_scene

def test_success_multiple_objects_apply(self):
EasyJB marked this conversation as resolved.
Show resolved Hide resolved
random.seed(1222)
expected_scene = Scene(players=[Player(player_id="dummy", player_resources={"Wood": 0, "Stone": 0}, drop_area=Vector(5, 5))])
expected_dummy_object = Object(id=1, name="dummy", owner="dummy", position=Vector(5, 5))
expected_dummy_drop = Drop(target_position=Vector(5, 6), object_id=1)
expected_dummy_object2 = Object(id=expected_scene.generate_id(), name="Wood", position=Vector(5, 6),
tags=['pushable', 'pickable'])
expected_dummy_object3 = Object(id=expected_scene.generate_id(), name="Wood", position=Vector(5, 6),
tags=['pushable', 'pickable'])
expected_dummy_object4 = Object(id=expected_scene.generate_id(), name="Stone", position=Vector(5, 6),
tags=['pushable', 'pickable'])
expected_scene.add_entity(expected_dummy_object)
expected_scene.add_entity(expected_dummy_drop)
expected_scene.add_entity(expected_dummy_object2)
expected_scene.add_entity(expected_dummy_object3)
expected_scene.add_entity(expected_dummy_object4)

random.seed(1222)
scene = Scene(players=[Player(player_id="dummy", player_resources={"Wood": 0, "Stone": 0}, drop_area=Vector(5, 5))])
dummy_object = Object(id=1, name="dummy", owner="dummy", position=Vector(5, 5), inventory={"Wood": 2, "Stone": 1})
dummy_drop = Drop(target_position=Vector(5, 6), object_id=1)
scene.add_entity(dummy_object)
scene.add_entity(dummy_drop)

dummy_drop.apply(scene)
for action in scene.pending_actions:
Action.deserialize(action).apply(scene)
scene.pending_actions.clear()

assert scene == expected_scene

def test_success_drop_to_drop_area_apply(self):
EasyJB marked this conversation as resolved.
Show resolved Hide resolved
expected_scene = Scene(players=[Player(player_id="test", player_resources={"Wood": 2, "Stone": 1}, drop_area=Vector(5, 6))])
expected_dummy_object = Object(id=1, name="dummy", owner="test", position=Vector(5, 5))
expected_dummy_drop = Drop(target_position=Vector(5, 6), object_id=1)
expected_update_points = UpdateResources(user_name="test", points_updated = {"Wood": 2, "Stone": 1})
expected_scene.add_entity(expected_dummy_object)
expected_scene.add_entity(expected_dummy_drop)
expected_scene.pending_actions.append(expected_update_points.serialize())

scene = Scene(players=[Player(player_id="test", player_resources={"Wood": 0, "Stone": 0}, drop_area=Vector(5, 6))])
dummy_object = Object(id=1, name="dummy", owner="test", position=Vector(5, 5), inventory={"Wood": 2, "Stone": 1})
dummy_drop = Drop(target_position=Vector(5, 6), object_id=1)
scene.add_entity(dummy_object)
scene.add_entity(dummy_drop)

dummy_drop.apply(scene)

assert scene == expected_scene
EasyJB marked this conversation as resolved.
Show resolved Hide resolved
class TestDropRequirements:

Krystian030 marked this conversation as resolved.
Show resolved Hide resolved
def test_success_requirements(self):
EasyJB marked this conversation as resolved.
Show resolved Hide resolved
scene = Scene(players=[Player(player_id="dummy", player_resources={"Wood": 0, "Stone": 0}, drop_area=Vector(5, 5))])
dummy_object = Object(id=1, name="dummy", owner="dummy", position=Vector(5, 5), inventory={"Wood": 1})
dummy_drop = Drop(target_position=Vector(5, 6), object_id=1)
scene.add_entity(Object(id=3, name="Grass", position=Vector(5, 6), tags=['walkable']))
scene.add_entity(dummy_object)
assert dummy_drop.satisfies_requirements(scene) is True

def test_fail_requirements_distance(self):
EasyJB marked this conversation as resolved.
Show resolved Hide resolved
scene = Scene(players=[Player(player_id="dummy", player_resources={"Wood": 0, "Stone": 0}, drop_area=Vector(5, 5))])
dummy_object = Object(id=1, name="dummy", owner="dummy", position=Vector(5, 5), inventory={"Wood": 1})
dummy_drop = Drop(target_position=Vector(6, 6), object_id=1)
scene.add_entity(Object(id=3, name="Grass", position=Vector(5, 6), tags=['walkable']))
scene.add_entity(dummy_object)
assert dummy_drop.satisfies_requirements(scene) is not True

def test_fail_requirements_inventory(self):
EasyJB marked this conversation as resolved.
Show resolved Hide resolved
scene = Scene(players=[Player(player_id="dummy", player_resources={"Wood": 0, "Stone": 0}, drop_area=Vector(5, 5))])
dummy_object = Object(id=1, name="dummy", owner="dummy", position=Vector(5, 5))
dummy_drop = Drop(target_position=Vector(5, 6), object_id=1)
scene.add_entity(Object(id=3, name="Grass", position=Vector(5, 6), tags=['walkable']))
scene.add_entity(dummy_object)
assert dummy_drop.satisfies_requirements(scene) is not True

def test_fail_requirements_walkable(self):
EasyJB marked this conversation as resolved.
Show resolved Hide resolved
scene = Scene(players=[Player(player_id="dummy", player_resources={"Wood": 0, "Stone": 0}, drop_area=Vector(5, 5))])
dummy_object = Object(id=1, name="dummy", owner="dummy", position=Vector(5, 5), inventory={"Wood": 1})
dummy_drop = Drop(target_position=Vector(5, 6), object_id=1)
scene.add_entity(Object(id=3, name="Grass", position=Vector(5, 6)))
scene.add_entity(dummy_object)
assert dummy_drop.satisfies_requirements(scene) is not True
1 change: 1 addition & 0 deletions tests/scene_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ class TestSceneSerialization:
'"tick": "", "on_death": "", "owner": "", "tags": [], "inventory": {}}}, ' \
'{"type": "Move", "attributes": {"object_id": 456, "direction": {"x": 1, "y": ' \
'0}}}], "pending_actions": []}'

def test_success(self) -> NoReturn:
scene = Scene()

Expand Down
1 change: 1 addition & 0 deletions tests/user_helper_functions_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ def test_filter_objects_failure(self) -> NoReturn:
assert filter_objects(self.scene, [1, 2, 3, 4], "empty") != [1]

def test_random_direction_success(self) -> NoReturn:
random.seed(12)
EasyJB marked this conversation as resolved.
Show resolved Hide resolved
self.scene.add_entity(Object(id=10, name="Grass", position=Vector(-1, 0), tags=["walkable"]))
self.scene.add_entity(Object(id=11, name="Grass", position=Vector(0, 1), tags=["walkable"]))
assert random_direction(self.scene, 1) == Vector(-1, 0)
Expand Down