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

Test item/avatar #23

Merged
merged 3 commits into from
Apr 19, 2023
Merged
Show file tree
Hide file tree
Changes from all 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
22 changes: 11 additions & 11 deletions game/common/avatar.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@


class Avatar(GameObject):
"""
'''
Notes for the inventory:

The avatar's inventory is a list of items. Each item has a quantity and a stack_size (the max amount of an
Expand Down Expand Up @@ -89,7 +89,7 @@ class Avatar(GameObject):
picked_up_item is left where it was first found.
Inventory after:
[inventory_item (5/5), inventory_item (5/5) inventory_item (5/5) inventory_item (5/5), inventory_item (5/5)]
"""
'''

def __init__(self, item: Item | None = None, position: Vector | None = None, inventory: list[Item] = [],
max_inventory_size: int = 10):
Expand Down Expand Up @@ -125,34 +125,34 @@ def max_inventory_size(self) -> int:
def held_item(self, item: Item | None) -> None:
# If it's not an item, and it's not None, raise the error
if item is not None and not isinstance(item, Item):
raise ValueError(f"{self.__class__.__name__}.held_item must be an Item or None.")
raise ValueError(f'{self.__class__.__name__}.held_item must be an Item or None.')
self.__held_item: Item = item

@score.setter
def score(self, score: int) -> None:
if score is None or not isinstance(score, int):
raise ValueError(f"{self.__class__.__name__}.score must be an int.")
raise ValueError(f'{self.__class__.__name__}.score must be an int.')
self.__score: int = score

@position.setter
def position(self, position: Vector | None) -> None:
if position is not None and not isinstance(position, Vector):
raise ValueError(f"{self.__class__.__name__}.position must be a Vector or None.")
raise ValueError(f'{self.__class__.__name__}.position must be a Vector or None.')
self.__position: Vector | None = position

@inventory.setter
def inventory(self, inventory: list[Item]) -> None:
if inventory is None or not isinstance(inventory, list) \
or (len(inventory) > 0 and any(map(lambda item: not isinstance(item, Item), inventory))):
raise ValueError(f"{self.__class__.__name__}.inventory must be a list of Items.")
raise ValueError(f'{self.__class__.__name__}.inventory must be a list of Items.')
if len(inventory) > self.max_inventory_size:
raise ValueError(f"{self.__class__.__name__}.inventory size must be less than max_inventory_size")
raise ValueError(f'{self.__class__.__name__}.inventory size must be less than max_inventory_size')
self.__inventory: list[Item] = inventory

@max_inventory_size.setter
def max_inventory_size(self, size: int) -> None:
if size is None or not isinstance(size, int):
raise ValueError(f"{self.__class__.__name__}.max_inventory_size must be an int.")
raise ValueError(f'{self.__class__.__name__}.max_inventory_size must be an int.')
self.__max_inventory_size: int = size

def pick_up(self, item: Item) -> Item | None:
Expand All @@ -169,15 +169,15 @@ def to_json(self) -> dict:
data: dict = super().to_json()
data['held_item'] = self.held_item.to_json() if self.held_item is not None else None
data['score'] = self.score
data['position'] = self.position
data['position'] = self.position.to_json() if self.position is not None else None
data['inventory'] = self.inventory
data['max_inventory_size'] = self.max_inventory_size
return data

def from_json(self, data: dict) -> Self:
super().from_json(data)
self.score: int = data['score']
self.position: Vector | None = data['position']
self.position: Vector | None = None if data['position'] is None else Vector().from_json(data['position'])
self.inventory: list[Item] = data['inventory']
self.max_inventory_size: int = data['max_inventory_size']
held_item: Item | None = data['held_item']
Expand All @@ -189,6 +189,6 @@ def from_json(self, data: dict) -> Self:
case ObjectType.ITEM:
self.held_item = Item().from_json(data['held_item'])
case _:
raise ValueError(f"{self.__class__.__name__}.held_item needs to be an item.")
raise ValueError(f'{self.__class__.__name__}.held_item needs to be an item.')

return self
120 changes: 78 additions & 42 deletions game/common/game_board.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import random
from typing import Self
from game.utils.vector import Vector
from game.common.stations.occupiable_station import Occupiable_Station
from game.common.stations.station import Station
from game.common.avatar import Avatar
from game.common.game_object import GameObject
from game.common.map.tile import Tile
Expand Down Expand Up @@ -102,50 +104,69 @@ def __init__(self, seed: int | None = None, map_size: Vector = Vector(),
locations: dict[tuple[Vector]:list[GameObject]] | None = None, walled: bool = False):

super().__init__()
self.seed = seed
# game_map is initially going to be None. Since generation is slow, call generate_map() as needed
self.game_map: list[list[GameObject]] | None = None
self.seed: int | None = seed
random.seed(seed)
self.object_type: ObjectType = ObjectType.GAMEBOARD
self.event_active = None
self.event_active: int | None = None
self.map_size: Vector = map_size
self.locations: dict = locations
# when passing Vectors as a tuple, end the tuple of Vectors with a comma so it is recognized as a tuple
self.locations: dict | None = locations
self.walled: bool = walled

# game_map is initially going to be None. Since generation is slow, call generate_map() as needed
self.game_map: list[list[GameObject]] | None = None


@property
def seed(self) -> int:
return self.__seed

@seed.setter
def seed(self, seed: int | None):
if seed is not None or not isinstance(seed, int):
raise ValueError("Seed must be an integer.")
def seed(self, seed: int | None) -> None:
if self.game_map is not None:
raise RuntimeError(f'{self.__class__.__name__} variables cannot be changed once generate_map is run.')
if seed is not None and not isinstance(seed, int):
raise ValueError(f'{self.__class__.__name__}.seed must be an integer.')
self.__seed = seed

@property
def game_map(self) -> list[list[GameObject]] | None:
return self.__game_map

@game_map.setter
def game_map(self, game_map: list[list[GameObject]]) -> None:
if game_map is not None and (not isinstance(game_map, list) or \
any(map(lambda l: not isinstance(l, list), game_map)) or \
any([any(map(lambda g: not isinstance(g, GameObject), l)) for l in game_map])):
raise ValueError(f'{self.__class__.__name__}.game_map must be a list[list[GameObject]].')
self.__game_map = game_map

@property
def map_size(self) -> Vector:
return self.__map_size

@map_size.setter
def map_size(self, map_size: Vector):
def map_size(self, map_size: Vector) -> None:
if self.game_map is not None:
raise RuntimeError(f'{self.__class__.__name__} variables cannot be changed once generate_map is run.')
if map_size is None or not isinstance(map_size, Vector):
raise ValueError("Map_size must be a Vector.")
raise ValueError(f'{self.__class__.__name__}.map_size must be a Vector.')
self.__map_size = map_size

@property
def locations(self) -> dict:
return self.__locations

@locations.setter
def locations(self, locations: dict[tuple[Vector]:list[GameObject]] | None):
if locations is not None or not isinstance(locations, dict):
def locations(self, locations: dict[tuple[Vector]:list[GameObject]] | None) -> None:
if self.game_map is not None:
raise RuntimeError(f'{self.__class__.__name__} variables cannot be changed once generate_map is run.')
if locations is not None and not isinstance(locations, dict):
raise ValueError("Locations must be a dict. The key must be a tuple of Vector Objects, and the "
"value a list of GameObject.")
for k,v in locations:
if len(k) != len(v):
raise ValueError("Cannot set the locations for the game_board. A key has a different "
"length than its key.")
if locations is not None:
for k,v in locations.items():
if len(k) != len(v):
raise ValueError("Cannot set the locations for the game_board. A key has a different "
"length than its key.")

self.__locations = locations

Expand All @@ -154,13 +175,15 @@ def walled(self) -> bool:
return self.__walled

@walled.setter
def walled(self, walled: bool):
def walled(self, walled: bool) -> None:
if self.game_map is not None:
raise RuntimeError(f'{self.__class__.__name__} variables cannot be changed once generate_map is run.')
if walled is None or not isinstance(walled, bool):
raise ValueError("Walled must be a bool.")
raise ValueError(f'{self.__class__.__name__}.walled must be a bool.')

self.__walled = walled

def generate_map(self):
def generate_map(self) -> None:
# generate map
self.game_map = [[Tile() for _ in range(self.map_size.x)] for _ in range(self.map_size.y)]

Expand All @@ -174,35 +197,33 @@ def generate_map(self):

self.__populate_map()

def __populate_map(self):
def __populate_map(self) -> None:
for k, v in self.locations.items():
if len(k) != len(v) or (len(k) == 0 or len(v) == 0): # Key-Value lengths must be > 0 and equal
raise ValueError("A key-value pair from game_board.locations has mismatching lengths. "
"They must be the same length, regardless of size.")

# random.choices returns a randomized list which is used in __help_populate()
j = random.choices(k, k=len(k))
# random.sample returns a randomized list which is used in __help_populate()
j = random.sample(k, k=len(k))
self.__help_populate(j, v)

def __help_populate(self, vector_list: list[Vector], v: list[GameObject]):
for i in v:
temp_vector: Vector = vector_list.pop()

def __help_populate(self, vector_list: list[Vector], v: list[GameObject]) -> None:
for j, i in zip(vector_list,v):
if isinstance(i, Avatar): # If the GameObject is an Avatar, assign it the coordinate position
i.position = temp_vector
i.position = j

temp_tile: GameObject = self.game_map[temp_vector.y][temp_vector.x]
temp_tile: GameObject = self.game_map[j.y][j.x]

while hasattr(temp_tile.occupied_by, 'occupied_by'):
temp_tile = temp_tile.occupied_by

if temp_tile is not None:
if temp_tile is None:
raise ValueError("Last item on the given tile doesn't have the 'occupied_by' attribute.")

temp_tile.occupied_by = i

def get_objects(self, look_for: ObjectType) -> list[GameObject]:
to_return = list()
to_return: list[GameObject] = list()

for row in self.game_map:
for object_in_row in row:
Expand All @@ -211,7 +232,7 @@ def get_objects(self, look_for: ObjectType) -> list[GameObject]:

return to_return

def __get_objects_help(look_for: ObjectType, temp: GameObject | Tile, to_return: list[GameObject]):
def __get_objects_help(self, look_for: ObjectType, temp: GameObject | Tile, to_return: list[GameObject]):
while hasattr(temp, 'occupied_by'):
if temp.object_type is look_for:
to_return.append(temp)
Expand All @@ -224,25 +245,40 @@ def __get_objects_help(look_for: ObjectType, temp: GameObject | Tile, to_return:

def to_json(self) -> dict:
data: dict[str, str] = super().to_json()
temp: list[list[GameObject]] = list((map(lambda tile: tile.to_json(), y)) for y in self.game_map)
temp: list[list[GameObject]] = list(list(map(lambda tile: tile.to_json(), y)) for y in self.game_map) if self.game_map is not None else None
data["game_map"] = temp
data["seed"] = self.seed
data["map_size"] = self.map_size
data["locations"] = self.locations
data["map_size"] = self.map_size.to_json()
data["location_vectors"] = [[vec.to_json() for vec in k] for k in self.locations.keys()] if self.locations is not None else None
data["location_objects"] = [[obj.to_json() for obj in v] for v in self.locations.values()] if self.locations is not None else None
data["walled"] = self.walled
data['event_active'] = self.event_active
return data

def generate_event(self, start, end):
def generate_event(self, start: int, end: int) -> None:
self.event_active = random.randint(start, end)

def from_json(self, data) -> Self:
def __from_json_helper(self, data: dict) -> GameObject:
match data['object_type']:
case ObjectType.WALL:
return Wall().from_json(data)
case ObjectType.OCCUPIABLE_STATION:
return Occupiable_Station().from_json(data)
case ObjectType.STATION:
return Station().from_json(data)
case ObjectType.AVATAR:
return Avatar().from_json(data)
# If adding more ObjectTypes that can be placed on the game_board, specify here
case _:
raise ValueError(f'The location (dict) must have a valid key (tuple of vectors) and a valid value (list of GameObjects).')

def from_json(self, data: dict) -> Self:
super().from_json(data)
temp = data["game_map"]
self.game_map: list[list[GameObject]] = list((map(lambda tile: Tile().from_json(tile), y)) for y in temp)
self.seed: int | None = data["seed"]
self.map_size: Vector = data["map_size"]
self.locations: dict[tuple[Vector]:list[GameObject]] = data["locations"]
self.map_size: Vector = Vector().from_json(data["map_size"])
self.locations: dict[tuple[Vector]:list[GameObject]] = {tuple(map(lambda vec: Vector().from_json(vec), k)) : [self.__from_json_helper(obj) for obj in v] for k, v in zip(data["location_vectors"], data["location_objects"])} if data["location_vectors"] is not None else None
self.walled: bool = data["walled"]
self.event_active = data['event_active']
self.event_active: int = data['event_active']
self.game_map: list[list[GameObject]] = list(list(map(lambda tile: Tile().from_json(tile), y)) for y in temp) if temp is not None else None
return self
6 changes: 4 additions & 2 deletions game/common/game_object.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import uuid

from game.common.enums import ObjectType
from typing import Self


class GameObject:
def __init__(self, **kwargs):
self.id = str(uuid.uuid4())
self.object_type = ObjectType.NONE

def to_json(self):
def to_json(self) -> dict:
# It is recommended call this using super() in child implementations
data = dict()

Expand All @@ -17,10 +18,11 @@ def to_json(self):

return data

def from_json(self, data):
def from_json(self, data: dict) -> Self:
# It is recommended call this using super() in child implementations
self.id = data['id']
self.object_type = data['object_type']
return self

def obfuscate(self):
pass
15 changes: 12 additions & 3 deletions game/common/items/item.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ class Item(GameObject):
def __init__(self, value: int = 1, durability: int | None = 100, quantity: int = 1, stack_size: int = 1):
super().__init__()
self.__quantity = None # This is here to prevent an error
self.__durability = None
self.object_type: ObjectType = ObjectType.ITEM
self.value: int = value # Value can more specified based on purpose (e.g., the sell price)
self.stack_size: int = stack_size # the max quantity this item can contain
Expand All @@ -31,8 +32,10 @@ def stack_size(self) -> int:

@durability.setter
def durability(self, durability: int | None):
if durability is not None and not isinstance(durability, int) or self.stack_size > 1:
raise ValueError(f'{self.__class__.__name__}.durability must be an int or None, and stack_size must be 1.')
if durability is not None and not isinstance(durability, int):
raise ValueError(f'{self.__class__.__name__}.durability must be an int or None.')
if durability is not None and self.stack_size != 1:
raise ValueError(f'{self.__class__.__name__}.durability must be set to None if stack_size is not equal to 1.')
self.__durability = durability

@value.setter
Expand All @@ -45,7 +48,7 @@ def value(self, value: int) -> None:
def quantity(self, quantity: int) -> None:
if quantity is None or not isinstance(quantity, int):
raise ValueError(f'{self.__class__.__name__}.quantity must be an int.')
if quantity < 0:
if quantity <= 0:
raise ValueError(f'{self.__class__.__name__}.quantity must be greater than 0.')

# The self.quantity is set to the lower value between stack_size and the given quantity
Expand All @@ -59,11 +62,17 @@ def quantity(self, quantity: int) -> None:
def stack_size(self, stack_size: int) -> None:
if stack_size is None or not isinstance(stack_size, int):
raise ValueError(f'{self.__class__.__name__}.stack_size must be an int.')
if self.durability is not None and stack_size != 1:
raise ValueError(f'{self.__class__.__name__}.stack_size must be 1 if {self.__class__.__name__}.durability '
f'is not None.')
if self.__quantity is not None and stack_size < self.__quantity:
raise ValueError(f'{self.__class__.__name__}.stack_size must be greater than or equal to the quantity.')
self.__stack_size: int = stack_size

def pick_up(self, item: Self) -> Self | None:
if item is None or not isinstance(item, Item):
raise ValueError(f'{item.__class__.__name__} is not of type Item.')

# If the items don't match, return the given item without modifications
if self.object_type != item.object_type:
return item
Expand Down
2 changes: 1 addition & 1 deletion game/common/map/wall.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@ class Wall(GameObject):
def __init__(self):
super().__init__()
self.object_type = ObjectType.WALL

2 changes: 1 addition & 1 deletion game/common/stations/station.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ def item(self) -> Item|None:
@item.setter
def item(self, item: Item) -> None:
if item is not None and not isinstance(item, Item):
raise ValueError(f"{self.__class__.__name__}.item must be an Item or None, not {item}.")
raise ValueError(f'{self.__class__.__name__}.item must be an Item or None, not {item}.')
self.__item = item

# take action method
Expand Down
Loading